Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Tutoriel et Help

- C O M -


COM Wiki

Component Object Model

Un article de Wikipédia, l'encyclopédie libre.

Component Object Model (abr. COM) est une technique de composants logiciels développée par Microsoft et DEC, depuis 1994, populaire sur Windows et également disponible sur les systèmes d'exploitation OpenVMS, Tru64 ainsi que d'autres Unix. Utilisée pour mettre en œuvre OLE et ActiveX, COM est dépassé depuis 2009 par le Framework .NET de Microsoft. Les technologies COM regroupent OLE, COM+, Distributed COM et ActiveX

  • 1 Fonctionnalités
  • 2 Caractéristiques
  • 3 Historique
  • 4 Produits connexes
  • 5 Notes et références
  • 6 Voir aussi

Fonctionnalités

Component Object Model est une spécification créée par Microsoft, qui décrit comment un programme exécutable peut être emballé dans un objet par un programmeur, permettant l'utilisation de l'objet par d'autres programmeurs. La spécification COM décrit une interface de programmation ainsi qu'un procédé de communication entre les objets qui est indépendant des langages de programmation.

Un des objectifs de COM est de faciliter la création de programmes par assemblage de composants, où chaque composant peut être mis à jour ou amélioré indépendamment des autres. Ceci est rendu possible par le fait que tous les composants peuvent être assemblés selon la même technique. COM unifie la manière dont les composants mettent à disposition leurs fonctionnalités et la manière dont les autres programmes vont les rechercher et s'en servir. Les programmes qui adhèrent à la spécification COM pourront être réutilisés dans plusieurs langages de programmation, tels que C, Visual Basic, Java, Delphi, FoxPro ou COBOL.

Caractéristiques

Un objet COM est essentiellement un programme qui met en œuvre une ou plusieurs interfaces de programmation. Chaque interface est une liste de fonctions. Toutes les interfaces sont dérivées de l'interface nommée IUnknown. Les objets COM peuvent être mis en œuvre par des fichiers .exe ou .dll de Microsoft Windows

L'interface IUnknown comporte trois fonctions: QueryInterface, AddRef et Release. La fonction QueryInterface permet à un programme de demander au composant COM les caractéristiques d'une interface qu'il met en œuvre. Les fonctions AddRef et Release servent à informer le composant qu'un programme est en train de s'en servir. Plusieurs programmes peuvent se servir simultanément d'un composant COM. Un composant COM est exécuté dès qu'un programme s'en sert, et s'arrête automatiquement lorsqu'il n'est plus utilisé par aucun programme.

Historique

Cette section ne cite pas suffisamment ses sources. Pour l'améliorer, ajouter en note des références vérifiables ou les modèles {{Référence nécessaire}} ou {{Référence souhaitée}} sur les passages nécessitant une source.

Un des pères de cette technologie fut Anthony Williams, qui la valorisa dans ses livres Object Architecture: Dealing With the Unknown - or - Type Safety in a Dynamically Extensible Class (1988) et On Inheritance: What It Means and How To Use It (1990).

OLE 1.0

La précédente technologie Microsoft orientée objet fut Object Linking and Embedding (OLE) 1.0, qui a été construit sur les Dynamic Data Exchange (DDE) et spécifiquement conçus pour les documents composés (par exemple lorsqu'un tableau est inséré dans un document Word des changements opérés sur le tableau Excel seront propagés dans le document Word). Cela fut introduit par Word et Excel en 1991 et dans Windows 3.1 un an plus tard. De même en 1991, Microsoft introduisit les contrôles Visual Basic, ou VBX grâce à Visual Basic 1.0.

OLE 2.0

En 1993, Microsoft sortit les OLE 2, et créa le COM comme modèle objet pour OLE 2. Alors que OLE 1 s'occupait principalement des documents composés, OLE 2 fut destiné à adresser des composants logiciels en général. En 1994, les contrôles OLE (OCX) furent introduits en tant que successeurs des VBX. Dans le même temps, Microsoft décida que OLE 2 s'appellerait simplement OLE, et qu'il ne désignait dès lors plus un acronyme, mais le nom des technologies composants de la firme.

En début d'année 1996, Microsoft renomma certaines parties de OLE concernant Internet, ActiveX, et petit à petit toutes les parties de OLE passèrent sous le signe ActiveX, exception faites des composants faits pour les documents composés et utilisés par exemple par Microsoft Office.

Produits connexes

COM sert de fondations aux technologies OLE et ActiveX [4] et est disponible dans divers langages de programmation. Les contrôles de Microsoft Foundation Classes (abr. MFC) sont des objets COM et Active Template Library est un ensemble de modèles pour créer des objets COM en langage C++[6]. DCOM et COM+ sont des logiciels de service qui étendent les possibilités d'utilisation des objets COM.

Java RMI, IBM SOM, et CORBA sont des technologies similaires à COM. CORBA offre des fonctionnalités semblables à COM/DCOM. Il existe des logiciels de pont qui permettent d'utiliser un objet COM comme s'il s'agissait d'un objet CORBA ou inversement.

Depuis 2003 la technologie .Net remplace la technologie COM, elle offre bon nombre de ses avantages tout en corrigeant certains problèmes. .Net et Visual Studio offrent diverses possibilités d'utilisation conjointe des technologies COM et .Net. Un objet COM peut être utilisé dans un programme en .Net, par le biais d'un wrapper qui lui donnera l'apparence d'un objet .Net et un objet en .Net peut être utilisé pour mettre en œuvre un composant COM par le biais d'un wrapper qui lui donnera l'apparence d'un composant COM.

Distributed COM

Article détaillé : Distributed Component Object Model.

DCOM est un logiciel middleware de type ORB : un logiciel moteur qui permet à des objets qui s'exécutent sur un ordinateur d'échanger des services avec des objets exécutés par un autre ordinateur. L´ORB fait appel à un ORB exécuté par l'autre ordinateur. Il permet en particulier au programmeur de se servir de l'objet sans tenir compte du fait qu'il est sur un autre ordinateur, DCOM traitant automatiquement les communications entre les deux ordinateurs, y compris l'application du protocole Remote Procedure Call, l'authentification, la sérialisation et l'utilisation de la mémoire cache.

COM+

COM+ est une suite intégrée de logiciels composée de COM, DCOM et Microsoft Transaction Server. Ce dernier met en œuvre un environnement d'exécution pour les composants COM avec des fonctionnalités de regroupement des threads et des connexions aux bases de données, ainsi que des fonctionnalités d'exécution automatique des composants COM. COM+ comporte un dispositif de contrôle d'accès à base de rôles, une console permet de manipuler un catalogue de composants. Une fois ajoutés au catalogue, ceux-ci peuvent être utilisés depuis un autre ordinateur, et un service se chargera d'arrêter l'exécution d'un composant en cas d'inactivité prolongée[12]. COM+ est incorporé dans Windows 2000.

OLE

Article détaillé : Object Linking and Embedding.

OLE (pour Object Linking and Embedding) vise à permettre d'incorporer un document dans un autre document. Cette fonctionnalité utilise initialement la technique de communication entre programmes Dynamic Data Exchange (abr. DDE).

La version 2.0 de OLE n'utilise plus la technique de communication DDE et la fonctionnalité est assurée par le partage d'un bloc de données entre différentes applications, le bloc en question est un objet COM. Un conteneur OLE est une application qui peut manipuler des blocs de données OLE, et un serveur OLE est une application qui peut produire des blocs OLE.

OLE 2.0 introduit une nouvelle fonctionnalité - Automation - qui permet à un programme de demander à un autre programme d'effectuer des opérations.

ActiveX

Article détaillé : ActiveX.

ActiveX est un buzzword [14] qui fait référence à une technologie destinée à transformer des pages web en applications qui, selon l'auteur, sont plus rapides que celles utilisant des applet Java. Dans les faits les objets ActiveX sont des objets COM avec quelques fonctionnalités de plus qui permettent de les utiliser sur le web[15]. Le nom ActiveX a été choisi pour se distancer de la fonction de liaison et incorporation d'objet (anglais Object Linking and Embedding abr. OLE) parce que l'objectif des contrôles ActiveX n'a plus rien à voir. Techniquement il n'y a pas de différence entre un objet COM et un objet ActiveX: l'objet met en œuvre les interfaces IUnknown et IDispatch, la fonction QueryInterface et la fonction DllRegisterServer.


Accueil

Accueil CHM

Normal, dit comme ça,
vous ne risquez pas de comprendre.


Accessing COM Objects from Assembly

Ernest Murphy ernie@surfree.com

Revised Dec 26 2000 for inclusion as part of MASM32

Revised July 10 2000 for the new form of coinvoke.

Sample code for this article is available at ...\COM\examples\shortcut

Abstract:
--------------------------------------------------------------------------------------------------------------------

The COM (Component Object Model) is used by the Windows operation system in increasing ways. For example, the shell.dll uses COM to access some of its API methods. The IShellLink and IPersistFile interfaces of the shell32.dll will be demonstrated to create a shortcut shell link. A basic understanding of COM is assumed. The code sample included is MASM specific.

Introduction:
--------------------------------------------------------------------------------------------------------------------

COM may seem complicated with it's numerous details, but in use these complications disappear into simple function calls. The hardest part is understanding the data structures involved so you can define the
interfaces. I apologize for all the C++ terminology used in here. While COM is implementation neutral, it borrows much terminology from C++ to define itself.

In order to use the COM methods of some object, you must first instance or create that object from its coclass, then ask it to return you a pointer to it's interface. This process is performed by the API function CoCreateInstance. When you are done with the interface you call it's Release method, and COM and
the coclass will take care of deleting the object and unloading the coclass.

A COM object is referred to as the SERVER. The program that calls up a COM object so it may use it is referred to as the CLIENT.

Assessing COM Methods
--------------------------------------------------------------------------------------------------------------------
To use COM methods you need to know before hand what the interface looks like. Even if you "late bind" through an IDispatch interface, you still need to know what IDispatch looks like. A COM interface is just table of pointers to functions. Let's start with the IUnknown interface. If you were to create a component that simply exports the IUnknown interface, you have a fully functional COM object (albeit on the level of "Hello World"). IUnknown has the 3 basic methods of every interface, since all interfaces inherit from IUnknown. Keep in mind all an interface consists of is a structure of function pointers. For IUnknown, it looks like this:

IUnknown STRUCT DWORD

; IUnknown methods
IUnknown_QueryInterface    QueryInterface_Pointer ?
IUnknown_AddRef            AddRef_Pointer ?
IUnknown_Release           Release_Pointer ?

IUnknown ENDS

That's it, just 12 bytes long. It holds 3 DWORD pointers to the procedures that actually implement the methods. It is the infamous "vtable" you may have heard of. The pointers are defined as such so we can have MASM do some type checking for us when compiling our calls. Since the vtable holds the addresses of functions, or pointers, these pointers are typedefed in our interface definition as such:

QueryInterface_Pointer typedef ptr QueryInterface_Proto

AddRef_Pointer          typedef ptr AddRef_Proto
Release_Pointer         typedef ptr Release_Proto

Finally, we define the function prototypes as follows:

QueryInterface_Proto    typedef PROTO :DWORD, :DWORD, :DWORD

AddRef_Pointer          typedef PROTO :DWORD
Release_Pointer         typedef PROTO :DWORD

In keeping with the MASM32 practice of "loose" type checking, function parameters are just defined as DWORDs. Lots of work to set things up, but it does keeps lots of errors confined to compile time, not run time. In practice, we will wrap up these interface definitions in include files and keep them from cluttering up the source code.

One rather big compilation on defining an interface: MASM cannot resolve forward references like this, so we have to define them backwards, by defining the function prototype typedefs first, and the interface table last. The include files for the example program later on defines the interfaces this way.

To actually use an interface, you need a pointer to it.

The CoCreateInstance API can be used to return us this indirect pointer to an interface structure. It is one level removed from the vtable itself, and actually points to the "object" that holds the interface. The final structure looks like this:

There is a lot of indirection using this structure, it can drive you batty trying to write code to properly reference and de-reference these elements. Macros to simplify this task will be defined.

When the client makes a call to the COM library to create a COM object, it passes in the address where it wants the object pointer to be placed. This initial pointer is generically referred to as "ppv," from the C++ speak "pointer to pointer to (void)," where (void) means an unspecified type. It holds the address of another pointer ("pv"), and this pointer refers to a whole table of pointers, one table entry for each function of the interface.

For example, say we used CoCreateInstance and successfully got an interface pointer ppv, and wanted to see if it supports some other interface. We can call its QueryInterface method and request a new ppv (ppv2, pointer to an Interface) to the other interface (pIID, pointer to a Interface Identifying GUID) we are interested in. In C, QueryInterface has a prototype that would look like so:

(HRESULT) SomeObject::QueryInterface (this:pObject, IID:pGUID, ppv2:pInterface)

Such a call would look like this:

; get pointer to the object
mov eax, ppv

; and use it to find the interface structure

mov edx, [eax]

; push the function parameters onto the stack

push OFFSET ppv2

push OFFSET IID_ISomeOtherInterface

push dword ppv

; and then call that method

call dword ptr [eax + 0]

This may be accomplished using the built-in MASM 'invoke' macro as such:

; get pointer to the object
mov eax, ppv

; and use it to find the interface structure

mov edx, [eax]

; and then call that method
invoke (IUnknown PTR [edx]).IUnknown_QueryInterface, ppv,

    ADDR IID_SomeOtherInterface, ADDR ppv_new

I hope you find this as wonderfully simple as I do.

Note we must pass in the pointer we used, this lets the interface know which object (literally "this" object) we are using.

Note the register must be type cast (IUnknown PTR [edx]). This lets the compiler know what structure to use to get the correct offset in the vtable for the .QueryInterface function (in this case it means an offset of zero from [edx]). Actually, the information contained by the interface name and function name called disappear at compile time, all that is left is a numeric offset from an as of yet value unspecified pointer.

One more semi-obscure point. Notice I changed the interface method name from simply "QueryInterface" to "IUnknown_QueryInterface". This is a bit of name decoration I've found necessary. When you get to larger COM projects with many similar interfaces you will run into a problem, that is different interfaces with identical method names. This is quite valid, in fact it's called polymorphism, but can confuse the compiler a bit.

Without this name decoration scheme things will be safe until you have two different interfaces with identical method names but different parameters to that method. This is more common then you might first think, but just consider how many interfaces might have a PRINT method.

The coinvoke Macro

--------------------------------------------------------------------------------------------------------------------
We can simplify a COM invoke further with a macro. This coinvoke macro is part of the oaidl.inc file.

;---------------------------------------------------------------------

; coinvoke MACRO

;

; invokes an arbitrary COM interface

;

; revised 12/29/00 to check for edx as a param and force compilation error

;                   (thanks to Andy Car for a how-to suggestion)

; revised 7/18/00 to pass pointer in edx (not eax) to avoid confusion with

;   parmas passed with ADDR  (Jeremy Collake's excellent suggestion)

; revised 5/4/00 for member function name decoration

; see http://ourworld.compuserve.com/homepages/ernies_world/coinvoke.htm

;

; pInterface    pointer to a specific interface instance

; Interface     the Interface's struct typedef

; Function      which function or method of the interface to perform

; args          all required arguments

;                   (type, kind and count determined by the function)

;

coinvoke MACRO pInterface:REQ, Interface:REQ, Function:REQ, args:VARARG

    LOCAL istatement, arg

    FOR arg, <args>     ;; run thru args to see if edx is lurking in there

        IFIDNI <&arg>, <edx>

            .ERR <edx is not allowed as a coinvoke parameter>

        ENDIF

    ENDM

    istatement CATSTR <invoke (Interface PTR[edx]).&Interface>,<_>,<&Function, pInterface>

    IFNB <args>     ;; add the list of parameter arguments if any

        istatement CATSTR istatement, <, >, <&args>

    ENDIF

    mov edx, pInterface

    mov edx, [edx]

    istatement

ENDM

;---------------------------------------------------------------------

Thus, the same QueryInterface method as before can be invoked in a single line:

coinvoke ppv ,IUnknown, QueryInterface, ADDR IID_SomeOtherInterface,

ADDR ppnew

Note that now the name decoration is done for us by the macro.

The only 'gotcha' (well, the most obvious) is that no parameters to a COM call should be passed in edx as this register is used to handle 'this' the object reference. Using edx as a parameter will generate a compile error.

Using IShellFile and IPersistFile from shell32.dll
--------------------------------------------------------------------------------------------------------------------
The shell32.dll provides a simple. easy way to make shell links (shortcuts). However, it uses a COM interface to provide this service. The sample below is based on the MSDN "Shell Links" section for "Internet Tools and Technologies."

This may be a strange place to find documentation, but there it is.

The "Shell Links" article may be found at: http://msdn.microsoft.com/library/psdk/shellcc/shell/Shortcut.htm

For this tutorial we will access the following members of the IShellLink and the IPersistFile interfaces. Note every interface includes a "ppi" interface parameter, this is the interface that we calling to (it is the THIS parameter). (The following interface information is a copy of information published by Microsoft)

IShellLink::QueryInterface, ppi, ADDR riid, ADDR ppv
* riid: The identifier of the interface requested. To get access to the
* ppv: The pointer to the variable that receives the interface.

Description: Checks if the object also supports the requested interface. If so,
signs the ppv pointer with the interface's pointer.

IShellLink::Release, ppi

Description: Decrements the reference count on the IShellLink interface.

IShellLink:: SetPath, ppi, ADDR szFile
* pszFile: A pointer to a text buffer containing the new path for the shell
link object.

Description: Defines where the file the shell link points to.

IShellLink::SetIconLocation, ppi, ADDR szIconPath, iIcon
* pszIconPath: A pointer to a text buffer containing the new icon path.
* iIcon: An index to the icon. This index is zero based.

Description: Sets which icon the shelllink will use.

IPersistFile::Save, ppi, ADDR szFileName, fRemember
* pszFileName: Points to a zero-terminated string containing the absolute path
of the file to which the object should be saved.
* fRemember: Indicates whether the pszFileName parameter is to be used as the
current working file. If TRUE, pszFileName becomes the current file and the
object should clear its dirty flag after the save. If FALSE, this save
operation is a "Save A Copy As ..." operation. In this case, the current file
is unchanged and the object should not clear its dirty flag. If pszFileName is
NULL, the implementation should ignore the fRemember flag.

Description: Perform a save operation for the ShellLink object, or saves the shell link are creating.

IPersistFile::Release, ppi
Description: Decrements the reference count on the IPersistFile interface.

These interfaces contain many many more methods (see the full interface
definitions in the code below), but we only need concentrate on those we will
actually be using.

A shell link is the MS-speak name for a shortcut icon. The information contained in a link (.lnk) file is:

1 - The file path and name of the program to shell.

2 - Where to obtain the icon to display for the shortcut (usually from the
executable itself), and which icon in that file to use. We will use
the first icon in the file

3 - A file path and name where the shortcut should be stored.

The use of these interfaces is simple and straightforward. It goes like this:

Call CoCreateInstance CLSID_ShellLink for a IID_IShellLink interface

Queryinterface IShellLink for an IID_IPersistFile interface.

Call IShellLink.SetPath to specify where the shortcut target is

Call IShellLink.SetIconLocation to specify which icon to use

Call IPersistFile.Save to save our new shortcut .lnk file.

finally,

Call IPersistFile.Release

Call IShellLink.Release

This releases our hold on these interfaces, which will automatically lead to the dll that supplied them being unloaded. Again, the hard part in this application was finding documentation. What finally found broke the search open was using Visual Studio "Search in Files" to find "IShellLink" and " IPersistFile" in the /include area of MSVC. This lead me to various .h files, from which I hand translated the interfaces from C
to MASM.

Another handy tool I could have used is the command line app "FindGUID.exe," which looks through the registry for a specific interface name or coclass, or will output a list of every class and interface with their associated GUIDs.

Finally, the OLEView.exe application will let you browse the registry type libraries and mine them for information. However, these tools come with MSVC and are proprietary.

Take care when defining an interface. Missing vtable methods lead to strange results. Essentially COM calls, on one level, amount to "perform function (number)" calls. Leave a method out of the vtable definition and you call the wrong interface. The original IShellLink interface definition I used from a inc
file I downloaded had a missing function. The calls I made generated a "SUCCEEDED" hResult, but in some cases would not properly clean the stack (since my push count did not match the invoked function's pop count), thus lead to a GPF as I exited a procedure. Keep this in mind if you ever get similar
"weird" results.

MakeLink.asm, a demonstration of COM
--------------------------------------------------------------------------------------------------------------------
This program does very little, as a good tutorial program should. When run, it creates a shortcut to itself, in the same directory. It can be amusing to run from file explorer and watch the shortcut appear. Then you can try the shortcut and watch it's creation time change.

The shell link tutorial code is in ...\COM\examples\shortcut. It begins with some "hack code" to get the full file name path of the executable, and also makes a string with the same path that changes the file to "Shortcut To ShellLink.lnk" These strings are passed to the shell link interface, and it is saved (or persisted in COM-speak).

The CoCreateLink procedure used to actually perform the COM methods and perform this link creation has been kept as general as possible, and may have reuse possibilities in other applications.

This program is similar to earlier published tutorial, but has been edited for some additional clarity. The interfaces are defined in a separate include file to reduce clutter. It may be built in MASM32 by using the ...\COM\bin\BLDDLL.BAT file supplied.

Additional note: Iczelion has quite a useful file in his tutorials named resource.h. It is quite useful when using rc.exe to compile resource files. I use it so much I have moved it to my /masm32/include/ folder. You need to either move your copy there, or change the path in the rsrc.rc file to build it properly.

Bibliography:
--------------------------------------------------------------------------------------------------------------------
"Inside COM, Microsoft's Component Object Model" Dale Rogerson
Copyright 1997,

Paperback - 376 pages CD-ROM edition
Microsoft Press; ISBN: 1572313498
(THE book for understanding how COM works on a fundamental level.
Uses C++ code to illustrate basic concepts as it builds simple fully
functional COM object)

"Automation Programmer's Reference : Using ActiveX Technology to Create
Programmable Applications" (no author listed)
Copyright 1997,

Paperback - 450 pages
Microsoft Press; ISBN: 1572315849
(This book has been available online on MSDN in the past, but it is cheap
enough for those of you who prefer real books you can hold in your hand.
Defines the practical interfaces and functions that the automation libraries
provide you, but is more of a reference book then a "user's guide")

Microsoft Developers Network

http://msdn.microsoft.com/

"Professional Visual C++ 5 ActiveX/Com Control Programming" Sing Li
and Panos Economopoulos
Copyright April 1997,

Paperback - 500 pages (no CD Rom, files available online)
Wrox Press Inc; ISBN: 1861000375
(Excellent description of activeX control and control site interfaces.
A recent review of this book on Amazon.com stated "These guys are the
type that want to rewrite the world's entire software base in
assembler." Need I say more?)

"sean's inconsequential homepage" http://ript.net/~spec/
Various hardcore articles on low-level COM and ATL techniques. Coded in C++

"Using COM in Assembly Language" Bill Tyler

http://thunder.prohosting.com/~asm1/
Assembly Language Journal, Apr-June 99


Inside coserver

Adapting Quick Editor for COM

Copyright © Dec 31, 2000 by Ernest Murphy ernie@surfree.com

For educational use only. All commercial use only by written license.

revised 3/5/01 for new Tools

The Build DLL, Build OCX, and Build LIB settings described may be found at ...\COM\BIN\

ABSTRACT

Several useful settings and bat files for the Quick Editor programming environment are discussed and offered as suggestions

INTRODUCTION

Quick Editor is a highly adaptable environment for building programming projects. Due to the menu configuration options many desirable features may be added. While some of these tricks and hints are solely applicable to COM projects, many (such as building a DLL) will see wider popularity.

The techniques to be discussed are:

· Building a Dynamic Link Library (.dll or .ocx)

· Building a Static linked library (.lib)

· Shelling Helper programs such as GUIDGEN, OleView, etc

· Registering and Unregistering COM components.

Building Dynamic Link Libraries

Dynamic link libraries are a quite useful build product, yet Quick Editor/MASM32 does not support them. This is easily remmedyied.

DLLs are produced at the LINK stage, so these following statements will produce the required filetype:

With Resources:

Link /DLL /SUBSYSTEM:WINDOWS /DEF:%1.def

          /LIBPATH:c:\masm32\lib %1.obj rsrc.obj

With No resources:

Link /DLL /SUBSYSTEM:WINDOWS /DEF:%1.def
                /LIBPATH:c:\masm32\lib %1.obj

These LINK directives should be all on the same line.

A .bat file BLDDLL.bat is provided to automate the entire build process, similar to the BLDALL.bat file does. This may be invoked with this Quick Editor menu macro:

Build &DLL,\MASM32\BIN\BLDDLL.bat {b}

A DLL also requires a .def file to specify the exported items. This file may also optionally name the final .dll Library. If it does not, the .asm file name is used with the .dll extension.

Building Visual Components (.ocx)

A Visual Component (.ocx) is simply a DLL with the extension changed to .ocx, no further magic is required. If the .def file specifies the library name as .ocx, then the DLL build as above may be used.

For those who prefer a choice, a BLDOCX.bat file option is included. This is identical to the BLDDLL.bat file, with the inclusion of an /OUT directive to change the Library name:

With Resources:

Link /DLL /SUBSYSTEM:WINDOWS /DEF:%1.def /OUT:%1.ocx

          /LIBPATH:c:\masm32\lib %1.obj rsrc.obj

With No resources:

Link /DLL /SUBSYSTEM:WINDOWS /DEF:%1.def /OUT:%1.ocx
                /LIBPATH:c:\masm32\lib %1.obj

These LINK directives should be all on the same line.

Build &OCX,\MASM32\BIN\BLDOCX.bat {b} {n}

Building Object Libraries

Another generally useful LINK option not included in Quick Editor/MASM32 is the object library. An object library is a set of pre-compiled procedures. They may be added by static linking to your project at Link time. Only those library routines actually required by the project are imported, making for the fastest, and smallest possible project build.

The MASM32lib is just such a library.

Libraries also require include files so that other projects can get references to the contained procedures. Typically, the .lib and the .inc will have the same name.

Library files may be built with the following series of commands:

\masm32\bin\ml /nologo /c /coff *.asm
\masm32\bin\lib *.obj /out:%1.lib

This builds a lib with the same name as the file you currently have open in Quick Editor. If you build the .lib with the .inc open you will get the correct .lib name.

Shelling Helper Applications

It is convienient to be able to shell helper applications directly from Quick Editor. This was your attention stays on the task at hand.

Generally, to add a helper application, open a shortcut to the app and inspect it's properties. You can copy the "Target" line, and use that as the Quick Editor path. Here are several examples from my Quick Editor settings:

OLEVIEW,\Program Files\Microsoft Visual Studio\Common\Tools\OLEVIEW.EXE

FindGUID,\MASM32\BIN\FINDGUID.BAT

GUID Generator,\Program Files\Microsoft
                 Visual Studio\Common\Tools\GUIDGEN.EXE

MSDN, \WINDOWS\HH.EXE \Program Files\Microsoft Visual
             Studio\MSDN98\98VS\1033\msdnvs98.col

All your folder paths may be different, these are only given as examples.

Registering and Unregistering COM Components

All COM components must be referenced in the registry before they will work, and it's also a good idea to unregister them if you go making changes in the design phase.

Being the lazy type, I made Quick Editor setting to do this for me. It takes the current file name, adds on either .dll or .ocx extensions, and registers or unregisters as you need be.

Register DLL,\MASM32\BIN\regsvr32.exe {n}.DLL

Unregister DLL,\MASM32\BIN\regsvr32.exe /u {n}.DLL

-

Register OCX,\MASM32\BIN\regsvr32.exe {n}.ocx

Unregister OCX,\MASM32\BIN\regsvr32.exe /u {n}.ocx

Note I have moved my copy of regsvr32 to my MASM32/BIN folder. Had it been in my path I would not have had to do this.

TOOLS FOR COM

New to CoLib 1.1 are two tools to simplify working with GUIDS is asm. AsmGUID.DLL will convert a registry formatted GUID string to asm style. GUIDGen will create a new GUID in asm format and paste it into your source.

Add the following to your TOOLS settings:

New GUID,GUIDGen.dll

Convert GUID,AsmGUID.DLL

These dlls must be moved to the same folder as Quick Editor.

Conclusion

The harder you make Quick Editor work for you, the easier your task will be. I hope I've spured you on to seek other improvements you can make.


Creating COM objects in ASM

Creating a COM object in ASM

Copyright © Dec 27, 2000 by Ernest Murphy ernie@surfree.com

For educational use only. All commercial use only by written license.

Revised December 27 for inclusion with MASM32

Sample code for this article is available at: .../COM/examples/MyCom

The Build DLL setting used for Quick Editor is at .../COM/BIN/BLDDLL.BAT

Abstract:

---------------------------------------------------------------------------------------------------------------------

The COM (Component Object Model) is based on a non-specific implementation standard (neither platform nor language is specified), but real world constraints of real computers add practical considerations to this standard.

Here, a simple yet fully functional COM object in-process server for the WinTel platform will be created. It will be tested in a Visual Basic environment to assure it's compliance to the standard.

---------------------------------------------------------------------------------------------------------------------The programs discussed in here is designed be assembled with the MASM32 package. The Visual Basic client was written in VB6, which still sets its form version as 5. If one edits the VB5 version to VB4 it might work in earlier versions, but this has not been tested. It should also work in any VBA application, but again this has not been tested.

I will make no attempt to explain the basics of the Component Object Model nor the COM contract here. I'm going to assume you are familiar with both, and such details as the vtable, the vtable pointer and such. If you are not so familiar, I suggest you check the previous articles here, or the best source of explanation is Dale Rogerson's "Inside COM" (see bibliography).

Since COM objects must run not in some textbook but on real computers, they must follow certain implementation standards specific to that computer and operating system. For a WinTel in-process server, there already exists a well-defined standard to load a blob of code and unload it when no longer needed: the humble dynamic link library.

From one view, all an in-process COM server consists of is a .DLL with a set of 5 well defined exports. These are:

DllMain: This is the first routine in any dll. It is called when the library is loaded. It should check that the client (the calling app) wants an in-process server, and fail if not. (COM supports other instancing choices, but this app does not.)

DllRegisterServer: The registry holds data on every COM object installed on the system. This routine self-registers the component in the registry. It is how regsvr32.exe can register a component, regsvr32 just calls this export and displays the return value.

DllUnregisterServer: When no longer needed, a component should be able to unregister itself. Again, regsvr32.exe will call this export to clean out the registry.

DllCanUnloadNow: Global variables in the server keep track of any objects created, and also if a lock was placed on the server (explained in IClassFactory.LockServer). The client app will periodically call this export to check if the server is no longer needed and then unload it.

DllGetClassObject: Finally the magic COM export. This export takes 3 parameters, the GUID of the component to be created, the GUID of that component's interface to be created, and a pointer to the object thus created. If either the component or the interface requested are not supported, this routine fails.

The first 4 exports are straightforward. DllGetClassObject is the new strange thing, and needs further comment.

By now, one thing you should have noticed is that COM is about nothing if it isn't about indirection. That indirection gives the power to the methods. In practice, the object returned from DllGetClassObject is not the object we seek: it is a "class factory" object. A class factory object is one that knows how to instance (create) another class. This first lever of indirection allows the details of the object's creation to be specified. If it simply and directly returned a pointer to the object, then the object already exists, thus we cannot set and control any parameters in its constructor.

DllGetClassObject returns a IClassFactory interface. IClassFactory inherits from IUnknown (of course, every interface does), as has these two member functions:

      HRESULT CreateInstance(

IUnknown * pUnkOuter,   //Pointer to outer object when part of an

// aggregate REFIID riid,

//Reference to the interface identifier

oid** ppvObject);       //Address of output variable that receives

// the interface pointer requested in riid

HRESULT LockServer(BOOL fLock);

//Increments or decrements the lock count

LockServer keeps the class factory instanced (helpful if one has to make numerous object copies). CreateInstance is the workhorse here, it is used to creates the object's "working" interface. Each class creates a class factory that solely knows how to create that particular class, since CreateInstance does not include a class reference ID. The class ID was specified when we created this class factory interface. Thus, a class factory only knows how to create interfaces of the class of which it is a member. It's sole purpose is to create the desired object class.

This indirection is useful if the class needs some special initiation. IClassFactory is necessary to handle aggregation (a topic I will have nothing more to add, and which the component made here does not support). Already, there is a definition for IClassFactory2, which checks licensing for the component.

To actually get your COM object, a client calls DllGetClassObject by invoking the CoCreateInstance API function. This API takes care of handling the class factory for you, and return the desired interface pointer. If you just need a plain vanilla object, this is the function to use. The CoGetClassObject API will return a pointer to IClassFactory is your class needs further creation parameters. Internally, CoCreateInstance itself calls CoGetClassObject and instance the class through IClassFactory, so you always need it define a class factory object creation interface.

Classes, Objects, you and THIS

---------------------------------------------------------------------------------------------------------------------I'm going to digress into some internal implementation details on how C++ handles objects. One can be a proficient C++ programmer and never fully understand this, as it is a level of complexity the compiler handles fully for you. However, the designers of COM took full advantage of the concept, and we need to understand this.

The new fundamental concept the ++ in C++ adds to the language is the concept of classes and objects. And object is an instance of a class. Let's explore what this means on a machine level. When we write a conventional program in asm, we depend on the compiler to create code and data segments for us. One area in memory is the code we execute, another holds the data we need. What the ++ in C++ does for you is dynamically reallocate data memory at run time, giving each instance of a class, each small segment of code it's own data segment. In a very real way, the instance of a class IS this data segment. Data specific to each instance (or copy) of the class is stored in this dynamic data area.

Perhaps you have heard that C++ passes an extra hidden parameter in function calls on an object, the THIS value. Whoever named this concept not only understood what was required, but had a good sense of humor. When one is writing low level code for an object (something the compiler usually does for you in C++), the first question one will have is "which object am I the code for?"

THIS is which object, one is always working with THIS object.

THIS is simply a pointer to the data memory area for THIS instance of the class. When an objects class function is called, THIS is silently passed on. When the private data of the object is accessed, the class code area uses THIS to reference where that data is for THIS instance.

Why is THIS important? Simply this: a pointer to a COM interface is same thing as the pointer to THIS. My Grammar checker is going nuts at me now, it may not be correct English but this is how THIS works.

In use, COM is simply a specification of interfaces. It says nothing about how they are actually implemented in code. In fact, it is written so such details are not defined such that any higher level language that implements these interfaces may be a COM server.

I defined my "class" data areas (the "objects") as thus:

; declare the ClassFactory object structure

ClassFactoryObject STRUCT

lpVtbl      DWORD 0 ; function table pointer

nRefCount   DWORD 0 ; object and reference count

ClassFactoryObject ENDS

; declare the MyCom object structure

MyComObject STRUCT

lpVtbl      DWORD 0 ; function table pointer

nRefCount   DWORD 0 ; reference count

nValue      DWORD 0 ; interface private data

MyComObject ENDS

The first point is I have great latitude in defining this structure. The only element that the COM contract imposes on it is that it contain a DWORD pointer to the vtable of functions. I also use it to hold the private data for each interface, that being the reference counts and values. nValue is the Value the MyCom interface works with, as we will see next. The dynamic memory for these structures is allocated with the API function CoTaskMemAlloc and released with CoTaskMemFree. These are exported by the ole32.dll, which has many other useful exports, such as checking for GUID equality, and converting GUIDs to and from character strings.

MyCom, a Simple Interface

---------------------------------------------------------------------------------------------------------------------To illustrate the workings of COM interface, we will create a simple interface called IMyCom (all interfaces in COM should have the "I" prefix, for Interface). This interface, like all COM interfaces, derives from IUnknown. This simple means its first three functions are QueryInterface, AddRef, and Release.

To add our custom interface members, we add the next three function members (in C styled prototypes):

HRESULT SetValue(long *pVal);

HRESULT GetValue(long newVal);

HRESULT RaiseValue(long newVal);

These functions allow us to check the functionality of our interface. SetValue and GetValue allow us to set and read a data member of our interface. RaiseValue is a member function that adds a value to this data. Thus, we can assure ourselves we really are accessing a fully functional object from VB.

These structures in memory look like this:

The client just holds a pointer (ppv) to this distributed structure. (The generic name for the ppv pointer comes from it's C++ definition of "pointer to pointer to (void).") The "object" data blob is dynamically allocated and initiated when we create instance the class. The vtable and the server functions are static, they are defined at compilation time.

One point to notice here is the vtable holds pointers to the functions, not the functions themselves. Thus, we can "override" an inherited function simply by changing what routine the vtable points to. I have to say "override" in quotes because the class definitions are just a mental concept in this ASM implementation, I did not write classes and inherit them in other classes. But the concept is the same.

In the example to follow, there are function "overrides" performed this way. The IClassFactory and IMyCom both inherit QueryInterface from IUnknown. But as they support different interfaces, they need different routines to return different results. Thus, there are two QueryInterface routines (QueryInterfaceCF and QueryInterfaceMC) pointed to from the different vtables.

For simplicity, the AddRef and Release are also customized as to which interface they support. This is not an issue with AddRef, but the Release functions differ in that the MyCom Release has to know how to destroy the MyCom object. A further refinement for the future is to declare all objects in the same way so the same function can delete them, and we could use a single implementation of Release for everything.

A minor point on calling COM interfaces from within the server: There is entirely no reason to go through the object table to get the pointer to a member function: INSIDE the server it's just another function, you can invoke it directly, as the code knows exactly where it is at compile time. Just think of it as an advanced optimization technique. This works because the calls all contain THIS (the object pointer) as a parameter, and hence answer the question "which" object with "THIS" object.

Another minor point here, each time a client makes a call on our object, it walks through memory from ppv to pv to the vtable to get the address of the function to invoke. Should you want to, you may change this final pointer during the object's lifetime to make it exhibit different characteristics. There is nothing in the COM contract to prohibit it, though it is akin to self-modifying code. I only mention it because previous writings claimed this not to be true.

Type Libraries

---------------------------------------------------------------------------------------------------------------------On WinTel platforms, every COM interface gets information on it stored in the system registry. These interfaces are created in something called "Interface Definition Language" (IDL) that may be compiled by MIDL (Microsoft Interface Definition Language) compiler, a command line app. I'm not going to pretend to you I fully understand IDL well enough to sit down in Notepad and define interfaces. But, since the MIDL tool is only shipped with MSVC, if you have it then you have VC, so I used the Visual Studio tool to create my original interface definition file.

I started an ATL project named MyComApp, and insert a new ATL object and choose Simple Object named MyCom (the same terms my app will use). The class wizard then created a blank IMyCom interface. The ATL Attributes need to be set to Single Thread, Custom Interface, and No Aggregation. Then I created the interface by right clicking the IMyCom interface in the Class browser, and using Add Property to insert the SetValue, GetValue properties, and RaiseValue method. Then I saved and closed the app and copied the MyComApp.idl file to my assembly program folder.

Here is the output of the VC ATL interface definition file (.idl):

// MyCom.idl : IDL source for MyCom.dll

//

// This file will be processed by the MIDL tool to

// produce the type library (MyCom.tlb) and marshalling code

import "oaidl.idl";

import "ocidl.idl";

[

object,

uuid(F8CE5E41-1135-11d4-A324-0040F6D487D9),

helpstring("IMyCom Interface"),

pointer_default(unique)

]

interface IMyCom : IUnknown

{

[propget, helpstring("property Value")]

HRESULT Value([out, retval] long *pVal);

[propput, helpstring("property Value")]

HRESULT Value([in] long newVal);

[helpstring("method Raise")]

HRESULT Raise(long Value);

};

[

uuid(F8CE5E42-1135-11d4-A324-0040F6D487D9),

version(1.0),

helpstring("MyComApp 1.0 Type Library")

]

library MyComLib

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

[

uuid(F8CE5E43-1135-11d4-A324-0040F6D487D9),

helpstring("MyCom Class")

]

coclass MyCom

{

[default] interface IMyCom;

};

};

This file can be used as a prototype for further interface definitions. Notice it contains 3 GUIDs, one each for the Interface, the coclass, and the type library. These MUST be changed and distinct for new applications.

This definition file should look pretty well self-explanatory, except for the interface itself:

[propget,
helpstring("property Value")] HRESULT Value([out, retval] long
*pVal);
[propput, helpstring("property Value")] HRESULT Value([in] long
newVal); [helpstring("method Raise")] HRESULT Raise(long
Value);

Here are the same interfaces defined in MASM:

GetValue PROTO :DWORD, :DWORD

SetValue PROTO :DWORD, :DWORD

RaiseValue PROTO :DWORD, :DWORD

BIG difference... but for a simple reason. Interfaces written for type libraries are as general as can be, and are directed at clients such as Visual Basic, and VB is designed to hold the programmer's hand as much as possible. To keep interfaces simple to VB users, the concept of a "property" is used. One may "set" or "get" a property value, so these two functions seem to be the same to a VB programmer (the object reference just moves to the other side of the equate operator). A "method" makes some change or performs some action involving the object.

To create the type lib, use MIDL on a command line like so:

MIDL MyCom.idl

This produces several output files which you can mostly ignore, and most importantly MyCom.tlb, our type library. This library should be added to the dll resource file with

1 typelib MyCom.tlb

Making it the first resource element is important, as later on we will be using the LoadTypeLib API function to extract this library, and this function expects to find the library at position 1 (unless told to do otherwise). So for simplicity, we keep it at position 1.

Registering the Component

---------------------------------------------------------------------------------------------------------------------The DllRegisterServer and DllUnregisterServer take care of registering the component for us. A dll (or ocx, which is really just a dll with a fancier extension). These registry entries are made:

HKEY_CLASSES_ROOT\CMyCom

(Default) "CMyCom simple client"

HKEY_CLASSES_ROOT\CMyCom\CLSID

(Default) "{A21A8C43-1266-11D4-A324-0040F6D487D9}"

HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}

(Default) "CMyCom simple client"

HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}\CMyCom

(Default) "CMyCom"

HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}\InprocServer32

(Default) "C:\MASM32\MYCOM\MYCOM.DLL"

ThreadingModel "Single"

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}

(Default) (value not set)

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0

(Default) "MyCom 1.0 Type Library"

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\0

(Default) (value not set)

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\0\win32

(Default) " C:\masm32\COM\MyCom \MYCOM.DLL"

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\FLAGS

(Default) "O"

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\HELPDIR

(Default) "C:\masm32\COM\MyCom"

One key value here is variable, that is the path and name of the server dll itself. On my system I placed it at "C:\MASM32\COM\MYCOM\MYCOM.DLL" This was detected when I registered the component, as one other function of DllRegisterServer is to discover where the dll itself is stored by invoking GetModuleFileName.

This is a lot of information for one little server. But all we need know to instance our server is pass the ID of {A21A8C43-1266-11D4-A324-0040F6D487D9} and a valid interface ID to CoCreateInstance. We need not know where it the component is, nor place it in a special directory. The CoCreate API's will trace through the registry settings, starting with the CLSID to discover all it needs to know to create the component. Once it has the component, it can load the type library from that to learn more if need be.

Fortunately for us, the last 5 registry entries are done for us via the RegisterTypeLib API. In DllRegisterServer we call a series of registry functions to set the first 5 keys and values, then invoke RegisterTypeLib. DllUnregisterServer just winds through this structure and deletes all the entries it made, then invokes UnRegisterTypeLib. When deleting keys, do take care NOT to delete the entire HKEY_CLASSES_ROOT\CLSID\ tree, as you will completely mess up your system and partially uninstall every other activeX component on your system.

A type library itself is defined as a "dense black blob" of binary data. The sole property of its internal structure revealed by Microsoft is the first 4 bytes shall be the ASCII code for "MSFT." To learn what is inside, API methods must be employed. Again, this keeps the COM contract language neutral.

Implementing the Unknown

---------------------------------------------------------------------------------------------------------------------

MyCom is a very simple object, it only impliments two interfaces, IUnknown and IMyCom. Since these two interfaces overlap, the returned ppv pointer need not be cast to either interface, and our very simple object structure will suffice. If you continue on to the CoLib (Component Library), you will see a much more involved object structure is required if multiple, non-overlapping interfaces are supported.

Object lifetime is handled by the IUnknown interface. These three seemingly simple methods of AddRef, Release, and QueryInterface are quite powerful, and are used such that the functionality of each is never duplicated in another section.

This non duplication of function is perhaps the impetus in why IUnknown was named such. When DllGetClassObject is invoked, the object CLSID and a specific interface IID are passed in to define what needs to be created. Think for a second: we are in effect asking DllGetClassObject to perform a QueryInterface on the object before we create it. That is not what happens, since we do not want to duplicate functionality (i.e., two identical QueryInterface implementations, one in the QueryInterface itself, one in DllGetClassObject). If nothing else, we would not want to maintain two sections of code that should have similar output.

Instead, when DllGetClassObject is invoked, we simply create the object defined by the CLSID. In effect, we create an unknown object. What is unknown is: Can this object support the interface we require?

That question is easily answered. DllGetClassObject will invoke QueryInterface on the unknown object. If it truly supports the interface, this reference is returned. If it does not support it, the object is deleted, and the DllGetClassObject returns a fail code.

AddRef is quite simple to implement. Since we have a simple object structure, and "this" is the base address of this structure, we can directly access all members of the object.

AddRef_MC proc this_:DWORD

mov eax, this_

inc (MyComObject ptr [eax]).nRefCount

mov eax, (MyComObject ptr [eax]).nRefCount

ret         ; note we return the object count

AddRef_MC endp

AddRef is a bit unusual in that it does not return a HRESULT (failure code), instead it returns the object count. The return value is undefined in the COM contract, but it is traditional to return the count.

Release not only has to decrement the object count, but when this count reaches zero it must both delete the object, and delete the object count of the dll (such that when the object count goes to zero the dll may be unloaded). Again, this is a trivial implementation:

Release_MC proc this_:DWORD

    mov eax, this_

    dec (MyComObject ptr [eax]).nRefCount

    mov eax, (MyComObject ptr [eax]).nRefCount

    .IF (eax == 0)

        ; the reference count has dropped to zero

        ; no one holds reference to the object

        ; so let's delete it

        invoke  CoTaskMemFree, this_

        dec MyCFObject.nRefCount

  xor eax, eax    ; clear eax (count = 0)

   .ENDIF

    ret         ; note we return the object count

Release_MC endp

MyCom is also a trivial interface to implement. The MyCom object has an extra member where the 'value' property is held.

GetValue proc this_:DWORD, pval:DWORD

    mov eax, this_

    mov eax, (MyComObject ptr [eax]).nValue

    mov edx, pval

    mov [edx], eax

    xor eax, eax        ; return S_OK

    ret

GetValue endp

SetValue proc this_:DWORD, val:DWORD

    mov eax, this_

    mov edx, val

    mov (MyComObject ptr [eax]).nValue, edx

    xor eax, eax        ; return S_OK

    ret

SetValue endp

RaiseValue  PROC this_:DWORD, val:DWORD

    mov eax, this_

    mov edx, val

    add (MyComObject ptr [eax]).nValue, edx

    xor eax, eax        ; return S_OK

    ret

RaiseValue  ENDP

MyCom.dll, the server code

---------------------------------------------------------------------------------------------------------------------To build the COM server use the BLDDLL.BAT file provided in "\masm32\COM\BIN" file under Quick Editor to compile. I suggest you change your editor menu settings to include a "Build DLL" option.

This project requires 5 files to build it:

MyCom.asm The main assembly code for the project

MyCom.idl Interface definition file, must be compiled to MyCom.tlb

MyCom.tlb Type Library, needed as a resource

rsrc.rc The resource file, just used to get the type library into the resource

MyCom.DEF Standard DLL export file

Once compiled, this code will do NOTHING, that is until you register it. The easiest way is to open a dos box to the folder where the dll is, and run: regsvr32 MyCom.dll. Alternatively, I have provided the bat files .r.bat and u.bat to register and unregister, respectivly, the MyCom component.

Running MyCom.dll through regsvr32 will invoke the DllRegisterServer export and write our information into the registry so we can...

Access the Server from Visual Basic

---------------------------------------------------------------------------------------------------------------------Make sure the blinds are drawn so the neighbors do not see you actually own Visual Basic. Open VB and start a Standard .Exe project. Look in the menu for Project | References and click it. Scroll through the list and check the box MyCom, and click OK. This adds the class ID to the VB application, and VB will look through the type library for further information on the server.

In the form designer, add textboxes Text1 and Text2 to the Form1, then add a command button Command1. Change the command caption to Raise. Now to the Form1 code area, and add the following:

Option Explicit

Private MC As New MyCom

Private Sub Command1_Click()

MC.Raise (Text2) Text1 = MC.Value

End Sub

Private Sub Form_Load()

Set MC = New MyCom

MC.Value = 100

Text1 = MC.Value

End Sub

Now you can run the application and test the server by clicking the Raise button. Do be careful, there is no error checking to see if you put a valid number in Text2. What you are seeing is Visual Basic running an assembly language server.

Note the sample program available for download is somewhat more complex, as it creates two copies of the server object to test. This demonstrates each object is capable of holding it's own private data information. It is quite true this server doesn't do all that much, but it is a baby step to full COM functionality from assembly.

Bibliography:

---------------------------------------------------------------------------------------------------------------------"Inside COM, Microsoft's Component Object Model" Dale Rogerson

Copyright 1997,

Paperback - 376 pages CD-ROM edition

Microsoft Press;

ISBN: 1572313498

(THE fundamental book on understanding how COM works on a fundamental level. Uses C++ code to illustrate basic concepts as it builds simple fully functional COM object)

"Automation Programmer's Reference : Using ActiveX Technology to Create Programmable Applications" (no author listed)

Copyright 1997,

Paperback - 450 pages

Microsoft Press;

ISBN: 1572315849

(This book has been available online on MSDN in the past, but it is cheap enough for those of you who prefer real books you can hold in your hand. Defines the practical interfaces and functions that the automation libraries provide you, but is more of a reference book then a "user's guide")

Microsoft Developers Network <http://msdn.microsoft.com>

"Professional Visual C++ 5 ActiveX/Com Control Programming" Sing Li and Panos Economopoulos Copyright April 1997,

Paperback - 500 pages (no CD ROM, files available online)

Wrox Press Inc;

ISBN: 1861000375

(Excellent description of activeX control and control site interfaces. A recent review of this book on Amazon.com stated "These guys are the type that want to rewrite the world's entire software base in assembler." Need I say more?)

"sean's inconsequential homepage <http://www.eburg.com/~baxters/>"

(Various hardcore articles on low-level COM and ATL techniques. Coded in C++)

"COM in Assembly Part II" http://asmjournal.freeservers.com Bill Tyler

(no copyright noted)

Assembly Programming Journal July-Sep 99

(Basic object creation and use through COM-like interfaces)


Inside coserver

Inside CoLib, a Component Object Library

Copyright © 2000 by Ernest Murphy ernie@surfree.com

For educational use only. All commercial use only by written license.

Revised 2/25/2001 for simplified class definition Version 1.1

ABSTRACT

"CoLib.Lib" is a library of functions to be used in the creation of COM servers. CoLib.Lib will make a dll server to support any number of COM objects with any number of COM interfaces. CoLib also provides a base to create COM objects in other environments, such as internal helper objects in either another COM object, or stand-alone. This document is an attempt to explain the logic behind this object file, and explain how to use it to create arbitrary COM objects.

CoLib.Lib is meant to be a reusable library of functions, to serve as the base of your object. It lets the programmer define what the object is, to create it and destroy it, supports the basic .DLL exports of a COM server, and the interfaces IUnknown, IClassFactory, and IDispatch. It optionally supports aggregation. CoLib will be an invaluable source of functionality for any in-process server.

CoLib is designed to work with the MASM32 assembly language package. This package is available free at http://www.pbq.com.au/home/hutch/masm.htm

Version 1.1 Notes

A few minor changes to CoLib were made for the first service revision. Most notably, the requirement to create an object image structure has been removed, you need just define your custom data structure. It was realized the ClassItem held enough information to infer the full object size given the custom data object, hence the ClassItem definition has been revised.

This should not break older code. However, classes defined per the original spec will create objects that will waste some memory space.

The registrar code was revised some, a string pointer deletion invoke was accidentally removed, though registering is still a problem in NT, though less so.

The lib files were further subdividing so it is more useful in creating objects on the client side, without linking to unused code. Previously, if you linked to AllocObject, you also got a DllGetClassObject implementation due to the way the files cross-linked. This has been corrected to give the smallest builds possible.

Several typographical errors (and some just plain bad stuff) were also corrected in this document.

INTRODUCTION

Do not try to draw any correlation between the coserver library structures and those used by the Active Type Library (ATL). These two libraries have similar goals, but completely different implementations. The CoLib library structures have different meaning then the ATL structures, and a comparison should not be made.

When describing how the CoLib works, the following definitions are used:

CoLib or library: functionality implemented by the CoLib.Lib library

Project: a full dll COM server built using the CoLib library. Also used to illustrate named particulars a project must supply

Client: A separate program that invokes the compiled project dll

this_: a reference to the COM object to be returned from the COM

server to a client

CoLib OBJECT CREATION

Under Windows, when a COM client needs a COM object, it will invoke the CoCreate-Instance API, passing in the Class Identifier (clsid) and Interface Identifier (iid) of the required object. The CoCreateInstance function will load the DLL, then invokes DLL exports DllMain followed by DllGetClassObject. DllGetClassObject needs to perform a check if this class clsid is supported.

Inside a CoLib dll, DllGetClassObject performs this check by walking a structure called a ClassMap. This Map is an array of ClassItem structures, each items describing a class the DLL supports. If a match between the requested class clsid and a supported clsid in the ClassMap is made, a Class Factory Object (CFO) is created.

This CFO contains a reference to the matching ClassItem, such that each CFO is associated with a particular class. Thus, a particular CFO is a factory for a single class only.

The CFO method IClassFactory::CreateInstance is then invoked by the CoCreateInstance API. The factory creates its referenced object, and casts it as IUnknown. The IUnknown::QueryInterface method of this unknown object is then invoked, requesting the interface iid parameter of the original CoCreateInstance invocation. If the interface is supported, a reference to it is passed back to the caller. If not, the object is deleted and a fail code is returned.

Each ClassItem contains a reference to an InterfaceMap, which is an array of InterfaceItem structures. Each item describes an interface supported by the object. This is how QueryInterface is able to determine what interfaces an object supports.

Briefly, that is how CoLib creates an object. The members of these class structures will be discussed in detail later on.

CLASS BASICS

Classes are defined in a structure called ClassItem. Each class defined in the project needs a ClassItem, which defines the requirements of the class. The fields of this structure are defined as so:

ClassItem STRUCT

m_clsid          DWORD ; reference to the Class ID (clsid) or

; GUID of this class

m_Flags           DWORD ; Control some basic object properties

; (See Object Flags)

m_pTypeLibInfo    DWORD ; reference to a TypeLibInfo structure for

; this class

m_pIMap           DWORD ; reference to an InterfaceMap for

; this class

m_pConstructor    DWORD ; pointer to custom class constructor

; function, may be NULL

m_pDestructor     DWORD ; pointer to custom class destructor

; function, may be NULL

m_pData           DWORD ; pointer to custom class data

; structure, may be NULL

m_ObjectSize      DWORD ; size of the custom ProjectData

; structure

ClassItem ENDS

This structure was redefined in CoLib version 1.1. Originally, m_ObjectSize defined the entire object size. This meant you had to custom define this object, not a simple task. It finally dawned on the author that only the custom data structure needs definition, as the other elements sizes may be determined either through SIZEOF at compile time, or counting at runtime.

Several interlocking structures are defined to provide all the information any class will need to define it's objects. All objects to be created through CoCreateInstance need have a ClassItem included in the ClassMap. Each project needs to define its custom ClassMap.

The ClassMap is an array of Class Item structures as so:

ClassMap:

            ClassItem1      ClassItem  { }

            ClassItem1      ClassItem  { }

            ...

            ClassItemN      ClassItem  { }

      END_CLASS_MAP

The END_CLASS_MAP macro is provided to flag the end of the Map list.

Classes also may also be instanced without a call to CoCreateInstance. If your class does not need to be instanced through this procedure, you need not include it in the ClassMap. All an object needs to be instanced is its describing ClassItem, and this reference may be passed to the AllocObject method to create helper or internal COM objects.

A reference to a TypeLibInfo structure provided for in the ClassItem structure. TypeLibInfo defines the Type Library to be used for a class. Type Libraries are very important for this implementation, as the CoLib IDispatch interface implementation needs this type information to perform its tasks. This parameter may be left NULL for objects that do not support IDispatch. See MSDN regarding the ITypeInfo interface to discover how a type library can support IDispatch.

TypeLibInfo   STRUCT

m_pIID_TYPELIB    DWORD       ; reference to the ID (refid) for

; this type lib

m_MajorVer        DWORD       ; Major Version of type lib

m_MinorVer        DWORD       ; Minor Version of type lib

TypeLibInfo   ENDS

Each object that may be served from the project dll will need its particular TypeLibInfo.

An InterfaceMap is an array of InterfaceItem structures. This allows each object to define which interfaces it supports, and to be cast to any of these interfaces.

InterfaceMap:

InterfaceItem1    InterfaceItem     { }

InterfaceItem2    InterfaceItem     { }

            ...

InterfaceItemN    InterfaceItem     { }

END_INTERFACE_MAP

The END_INTERFACE_MAP macro is provided to flag the end of the Map list.

Each object that may be served from the dll will need its particular InterfaceMap. Each InterfaceItem will need to refer to it's iid and also hold a pointer to the table of member functions of the interface.

InterfaceItem STRUCT

m_refiid          DWORD       ; reference to the Interface ID (iid)

; the GUID of this interface

m_pVtbl           DWORD       ; pointer to implementation table ('virtual'

; function vtable , or just vtable) of this

; interface

InterfaceItem ENDS

OBJECT BASICS

As previously discussed elsewhere (see "Creating COM objects in ASM," Assembly Programming Journal, Issue 8: Mar-Aug 2000), an object should be thought of a run-time allocated area of memory. It needs a well defined structure so it's internal properties may be employed to give custom instance information and access to the methods of the object.

In the CoLib.Lib library, an object is defined as an array of structures, build up as so:

      ProjectObject     STRUCT

            ObjectData1       { }

            ProjectData       { }

            ObjectEntry0      { }

            ObjectEntry1      { }

            ObjectEntry2      { }

      ...

            ObjectEntryN      { }

      ProjectObject     ENDS

These internal structures are used as follows:

ObjectData Contains the internal state of a particular object, such as the reference count, a reference to class data and such.

ProjectData An arbitrary structure determined by the specific project, meant to be a catch-all for all object-specific data.

ObjectEntryX The ObjectEntry structure provides a uniform way to support multiple interfaces. The value of this_ at any time is in fact a pointer to a particular ObjectEntry. This structure is used to cast this_ internally to the base address of the object.

In CoLib version 1.1 and higher this structure need not be defined. Just be aware that this is how your object is created by the lib.

The ObjectData is a standard structure holds the basic instance information for the object.

ObjectData STRUCT

m_RefCount DWORD ; The object reference count

m_pUnkOuter DWORD ; aggregating object's IUnknown

m_lcid DWORD ; current LCID of this object

m_pti DWORD ; object ITypeInfo pointer

m_pAggList DWORD ; reference to aggregated object linked list

m_pClassItem DWORD ; ClassItem data pointer

m_pEntry0 DWORD ; first ObjectEntry (to skip custom data)

ObjectData

The ProjectData structure is custom to each class and must be defined by the project for each class. There are no limits on the size or makeup of this structure. Since both this data structure and the methods are created together in a project, it is simple to allocate any private data required.

The ProjectData structure is a runtime creation, thus you will never need to create a .data structure of type ProjectData. The structure definition is quite useful for defining and accessing the private data when writing the custom method code. The size of this structure (m_ObjectSize) is used in the ClassItem.

The ObjectEntry structure is used to either direct a method call to the implementation table, or to cast this_ to the base address of the object itself.

ObjectEntry  STRUCT

    m_pVtbl         DWORD     ; interface object

    m_pBase         DWORD     ; pointer to base

ObjectEntry  ENDS

When CoLib creates an object, it included (N+1) ObjectEntry structures to support N interfaces. The extra ObjectEntry is needed to support the two IUnknown interfaces for aggregation (see the discussion following on aggregation).

Note that the ProjectObject structure itself is only instanced at runtime, no instance of the project object structure is made. Nor is there any ProjectObject structure defined anywhere. It is just a convenient fiction, used only to see how the of memory is organized at runtime.

OBJECT CONSTRUCTION AND DESTRUCTON

To allow for custom object creation and destruction, you may define functions to handle these events. This is a convent way to allocate and deallocate resources.

These functions should be written to the following prototypes:

ConstructorProto  PROC this_:DWORD, pCustomData:DWORD

DestructorProto   PROC this_:DWORD

The Constructor proto includes a pointer to a data structure you may customize for this class. This pointer is included in the AllocObject method. When called from DllGetClassObject, there is no way to customize this pointer, but it may be defined as needs be in a sub-object creation. (This prototype and definition was corrected for CoLib 1.1)

Thus. classes are provided two separate ways to be customized. The constructor accepts a reference to custom data, and there is also a undefined data pointer in the ClassItem itself. Alternate methods of customization are provided since a class may be instanced in two ways, and may be useful with a general prototype class, one that just requires a little extra bit of custom information. An enumeration interface is one example of this.

CASTING AN OBJECT FOR AN INTERFACE

To "cast" an object in assembler does need some explanation. The term "cast" is borrowed from C++, where it does make some concrete sense, but here in the assembly end, the concepts of class and object are a bit fuzzier.

The COM interface contract allows an object to support multiple interfaces. This interface has a well-defined binary structure that we can easily duplicate in assembly, but this opens the question of interface casting.

A much better explanation of this may be found on page 44 of "Inside COM," "Multiple Inheritance and Casting."

Let us look at the interface for an imaginary object. An interface is a table of pointers to functions. Say Object this imaginary object contains Interfaces IX and IY. As this is a COM object, both IX and IY inherit from IUnknown. Let us give both IX and IY a single method each, IX.Foo and IY.Bar. Also remember that this_ holds a reference to a pointer to the virtual table of functions. The binary layout of this interface would look like so:

CLIENT | OBJECT | OBJECT |
COM | INSTANCE | IMPLIMENTATION |
POINTER |  |  |
 |  | IX::QueryInterface | IX
this_ | pvtable1 | IX::AddRef |
 | pvtable2 | IX::Release |
 |  | IX::Foo |
 |  | IY::QueryInterface | IY
 |  | IY::AddRef |
 |  | IY::Release |
 |  | IY::Bar |

Figure 2. Interface casting detail (or why this_ is sometimes that_).

The first thing to notice from this figure is that there are two possible values of this_, one for each interface, and remember, it's not possible to call them "this_" and “that_". One little known property of casting is that the actual numeric value returned from a cast may not be equal to what was cast. This is another way of saying a cast may do more then just adjusting for different data formats.

When QueryInterface is invoked to get another interface inside an object, the returned pointer changes. this_ actually changes value. It has to, it must. This is completely normal. The only requirement COM imposes on the returned pointer is that a QueryInterface on two separate interfaces for IUnknown return the same value, so object identity may be determined.

This is important to us in the following way: we need a method to pass back this_, a cast interface pointer, cast to any arbitrary interface we support, and at the same time, use this_ to unwind our entire object structure. We need to get to all the information of the object, since all our code initially knows about it's instance is literally this_, and if this_ can change it means we cannot simply add an offset to this_ to get to other elements in the object structure.

Interface casting is performed like so: In our object, each vtable pointer is kept in a ObjectEntry structure, which has two elements, the vtable pointer immediately followed by the base address of our object. The base address is the starting address of the object, it's the address of the ObjectData structure.

Also, the ObjectData structure contains the address of the first ObjectEntry structure.

In this way, given this_ we can walk about our object and get to any structure from any this_ cast via code such as follows:

SomeInterface_SomeFunct PROC this_:DWORD, param1:DWORD, param2:DWORD

            mov ecx, this_

            mov eax, [ecx + 4]            ; ObjectData pointer in eax

Simple and direct. In actual use, we cast ecx at an ObjectEntry pointer so we don't have to remember the binary layout. Thus the actual library code to cast this_ to the custom data area is like so:

pObjectBase MACRO pObject:REQ, reg:REQ, _offset:VARARG

    .code

    ; cast object to the base ClassItem interface

    mov reg, pObject                                ; get object

    mov reg, (ObjectEntry PTR[reg]).m_pBase         ; walk to base data

    IFNB <_offset>

        add reg, _offset

    ENDIF

ENDM

Once we have these references to the object's internal structures, we can access any and all parts of it.

Casting for an interface is performed in only one place: the IUnknown::QueryInterface implementation. Back when the object was first created, the ClassMap was used to get reference to the InterfaceMap. The InterfaceItem held the interface ID iid, and a pointer to that vtable. These pointer values were then copied in order over into our object. When QueryInterface is given an interface to cast, it compares the given iid parameter with this InterfaceMap, looking for a match. If interface iid N matches the parameter, then the object is cast to it by returning the address of ObjectEntryN, thus making this_ point to the requested interface.

AGGREGATION

When an object is aggregated, it uses the IUnknown implementation of the containing object. If not aggregated, it uses it's own code for these functions. Aggregation is a runtime choice, so this change must be done on the fly.

CoLib uses the same method as found in "Inside COM," Chapter 8, "Component Reuse: Containment and Aggregation." Briefly, this entails having two separate IUnknown implementations, a delegation and non-delegating IUnknown. The non-delegating version does what we've come to expect from IUnknown, reference count and interface cast. The delegating IUnknown looks for another IUnknown implementation to pass it's work off to.

At object creation time, pUnkOuter is checked for NULL. If not NULL, it is stored in the object and used by the delegating IUnknown to do the work. If it is NULL, a reference to the very first ObjectEntry is saved instead. The first ObjectEntry is always automatically assigned to the non-delegating IUnknown.

If none of this made sense, you really need not worry, the library does this all for you. If you're still worrying, go check out "Inside COM."

IMPORTING THE LIBRARY

The coserver files need to be added to your masm32 area. For consistency sake, it is recommended that you place the library .inc files in \masm32\com\include, and the library itself in \masm32\com\colib. To use the library, you will first need to include the library itself (and it's own dependencies):

include           \masm32\include\masm32.inc

include           \masm32\com\include\oaidl.inc

include           \masm32\com\include\CoLib.inc

includelib        \masm32\lib\masm32.lib

includelib        \masm32\com\colib\CoLib.lib

The main project, if a dll server, should also export a ClassMap for each coclass:

      PUBLIC  ClassMap

CoLib.Lib depends on the linker to add those components a project requires, making it simple to override CoLib methods if you choose to implement anything differently. The CoLib.inc just defines some of the basic structures, it does not make .code or .data.

USING OTHER FEATURES OF THE LIBRARY

The following macros of CoLib.inc expand to code (not procedures) (they act as inline functions) and may be used anywhere in your code:

pObjectBase pObject, reg, _offset Used to cast this_ to the base address of the

object so ObjectData may be accessed.

Rarely used, except inside to the library itself

            pObject                      the this_ to cast

reg                     register to place result

_offset                 optional parameter, additional offset to

add to cast

pObjectData pObject, reg, _offset Used to cast this_ to the base address of the

custom data structure so the custom data

may be accessed.

      pObject                 the this_ to cast

reg                     register to place result

_offset                 optional parameter, additional offset to

add to cast

DeclareGuid gName, sIID       Used to instance a GUID textequate. Will

assign a GUID textequate to a guid name,

and also define a pointer to this guid.

gName             Label name for this guid. Will also assign

"p" & "gName" to create a reference

to the guid.

sIID                    optional parameter, used to define the

textequate for the guid if this does not

exist elsewhere.

DeclareGuid is handy to reduce typing (and hopefully improve clarity). For example:

DeclareGuid IID_IUnknown, sIID_IUnknown

will generate the following symbols:

      IID_Unknown       GUID    {000000000H, 00000H, 00000H, {0C0H, 000H, \

                                 000H, 000H, 000H, 000H, 000H, 046H}}

      pIID_Unknown      OFFSET  IID_Unknown

The second parameter is optional. If sIID_IUnknown is defined elsewhere and is of the form "s" & "guid name" then the macro will supply the equate. sIID is included for the occasion one needs to define a new guid number.

A DeclareVARIANT macro is provided to correctly initialize a VARIANT structure. These can be messy on your own. For example:

      DeclareVARIANT MyVariant, VT_I4, 10

will generate the following symbols:

      MyVariant VARIANT {VT_I4, , , , {10}}

OTHER REQUIRED PROJECT FILES

A project will also require the following files to completely build and define a COM server dll. See example project for samples and further explanation.

Interface Definition file .idl Defines the object to produce a type library

Type Library .tlb Produced by the .idl file, a black

box of object information

registry script .rgs Standard script for dll registration or

unregistration. See "Creating Registrar Scripts" in MSDN for .rgs syntax and usage. The library follows the same format.

rcrs.rc .rc Standard masm32 resource file. A starter

template is provided, this should be edited to your own needs as to your particular

typelib and registrar script filenames.

Project.def .def DLL export definitions.

RELATED FILES

· CoLib.inc | structures, constants, and macro functions needed by the CoLib library, include with your server project.
· component.inc | perhaps a continual work in progress, defining many constants, structures, and interfaces (54 and counting) for .ocx controls.
· L.inc | macro to define Unicode string constants
· language.inc | Equates and macros to support localization (help to interpret lcid parameters)
· mini_win.inc | Striped to the bone windows.inc file to speed the CoLib.Lib building process
· oaidl.inc | Basic definitions for any ASM COM project
· olectl.inc | Basic definitions for an .ocx project
· colibl.rc | Common resource definitions

APPENDIX A

EXPORTED LIBRARY METHODS

Standard dll Exports

DllMain PROTO :DWORD, :DWORD, :DWORD

DllRegisterServer PROTO

DllUnregisterServer PROTO

DllCanUnloadNow PROTO

DllGetClassObject PROTO :DWORD, :DWORD, :DWORD

DllRegister/Unregister Internal Functions

Register PROTO :DWORD

SetSubKey PROTO :DWORD, :DWORD, :DWORD

GetNextToken PROTO

AddReplacements PROTO :DWORD

GuardedDeleteKey PROTO :DWORD, :DWORD

Standard Interface Implementations

IUnknown Interface Implementations

QueryInterface PROTO :DWORD, :DWORD, :DWORD

AddRef PROTO :DWORD

Release PROTO :DWORD

NDQueryInterface PROTO :DWORD, :DWORD, :DWORD

NDAddRef PROTO :DWORD

NDRelease PROTO :DWORD

IDispatch Interface Implementations

GetTypeInfoCount PROTO :DWORD, :DWORD

GetTypeInfo PROTO :DWORD, :DWORD, :DWORD, :DWORD

GetIDsOfNames PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD

Invoke_ PROTO :DWORD, :DWORD, :DWORD, :DWORD, :WORD, :DWORD,

:DWORD, :DWORD, :DWORD

IDispatch Interface Helper Function

GetCachedTypeInfo PROTO :DWORD, :DWORD, :DWORD

Class Factory Interface Implementations

CreateInstance PROTO :DWORD, :DWORD, :DWORD, :DWORD

LockServer PROTO :DWORD, :DWORD

Class Factory Internal Functions

AllocObject PROTO :DWORD, :DWORD, :DWORD, :DWORD

DeallocObject PROTO :DWORD

External Helper Functions

ComPtrAssign PROTO :DWORD, :DWORD

ComQIPtrAssign PROTO :DWORD, :DWORD, :DWORD

APPENDIX B

LIBRARY DATA STRUCTURES

ClassItem

ClassItem STRUCT

m_clsid DWORD 0 ; CLSID; GUID of this class

m_Flags DWORD 0 ; various flags

m_pTypeLibInfo DWORD 0 ; type info map

m_pIMap DWORD 0 ; InterfaceMap for this object

m_pConstructor DWORD 0 ; custom object constructor function

m_pDestructor DWORD 0 ; custom object destructor function

m_pData DWORD 0 ; custom object data reference

m_ObjectSize DWORD 0 ; byte count of the

; private data structure

ClassItem ENDS

Contains parameters for the ClassMap array elements to define a class supported by the project dll.

m_clsid

Class ID (clsid) for this class.

m_Flags

Various flags controlling the object. This member can include zero or a combination of the following values:

AGGREGATABLE | Object will allow itself to be aggregated
DISPINTERFACE | Object will support IDispatch
SUPPORT_ERROR_INFO | Object will supply IErrorInfo for early bound clients. Currently not implemented.
SUPPLY_TYPE_INFO | Object will supply ITypeInfo through it's IDispatch. Do not include in objects that hide their interface.

m_pTypeLibInfo

Reference to the TypeLibInfo structure for this class.

m_pIMap

Reference to the InterfaceMap for this class.

m_pConstructor

Function pointer for the class creator function. Used to create any class-specific resources. May be NULL if no constructor is supplied.

m_pDestructor

Function pointer for the class deletion function. Used to clean up any class-specific resources. May be NULL if no destructor is supplied.

m_pData

Structure pointer for custom class information. It is entirely up to the specific class to determine how this parameter is used. May be NULL if no data is required.

m_ObjectSize

SIZEOF the specific class object structure. See ProjectObject.

InterfaceItem

InterfaceItem STRUCT

m_refiid DWORD 0

m_pVtbl DWORD 0

InterfaceItem ENDS

Contains parameters for an InterfaceMap item. Each interface of a class needs this definition.

m_refiid

refid for the GUID of this interface.

m_pVtbl

pointer to the implementation table for this interface.

ObjectData

ObjectData STRUCT

m_RefCount DWORD

m_pUnkOuter DWORD

m_lcid DWORD

m_pti DWORD

m_pAggList DWORD

m_pClassItem DWORD

m_pEntry0 DWORD

ObjectData

Contains basic instance information for an object. Each instance of a class needs this definition.

m_RefCount

reference count of the object

m_pUnkOuter

aggregating object's IUnknown

m_lcid

current LCID of this object

m_pti

object type library info pointer

m_pAggList

pointer to first aggregated object

m_pClassItem

reference to the ClassItem of this object

m_pEntry0

reference to first EntryItem entry (to index past the custom data area)

ObjectEntry

ObjectEntry STRUCT

m_pVtbl DWORD

m_pBase DWORD

ObjectEntry ENDS

Contains parameters for the TypeLibInfo structure to define a type library for a ClassItem class definition structure.

m_pVtbl

reference to the virtual table of functions for this interface

m_pBase

reference to base address of this object

TypeLibInfo

TypeLibInfo STRUCT

m_pIID_TYPELIB DWORD 0

m_MajorVer DWORD 0

m_MinorVer DWORD 0

TypeLibInfo ENDS

Contains parameters for the TypeLibInfo structure to define a type library for a ClassItem class definition structure.

m_pIID_TYPELIB

refid for this type lib.

m_MajorVer

Major Version of type lib.

m_MinorVer

Minor Version of type lib.

APPENDIX C

DATA STRUCTURES IMPLEMENTED BY THE PROJECT

ClassMap

ClassMap:

ClassItem1 ClassItem { }

ClassItem2 ClassItem { }

...

ClassItemN ClassItem { }

    END_CLASS_MAP

A ClassMap structure to define all classes supported by a particular dll. This array is particular to a given dll, and must be defined by the project employing the library. A ClassItem is used to define the ClassMap members, where the individual ClassItem structures are specified.

ClassItemN

An ClassItem structure for a particular class. Each class needs its ClassItem to be defined.

InterfaceMap

InterfaceMap EQU InterfaceItem1

InterfaceItem1 InterfaceItem { }

InterfaceItem2 InterfaceItem { }

...

InterfaceItemN InterfaceItem { }

END_OF_INTERFACEMAP

A InterfaceMap structure to define all interfaces supported by a particular class. This array is particular to a given class, and each class must have an InterfaceMap defined by the project. An InterfaceItem is used to define the InterfaceMap elements, where the individual InterfaceItem structure is specified.

InterfaceMap

The InterfaceMap is a reference to the first element in the map array.

InterfaceItem

A InterfaceItem structure for a particular interface. Each interface in a class needs its InterfaceItem to be defined.

END_OF_INTERFACEMAP

A macro to define a NULL InterfaceItem element to signify the end of the InterfaceMap.

ProjectData

ProjectData STRUCT

m_DataItem1 DWORD ?

m_DataItem2 DWORD ?

m_DataItem3 DWORD ?

m_DataItem4 DWORD ?

ProjectData ENDS

ProjectData is given as an example only. Each class must have an object data structure to hold its custom members. ProjectData is an example of the layout of this structure

m_DataItemN

This structure, and the data defined are arbitrary, for example only. Each particular class needs to define its own custom data structure in the project file.

ProjectObject

ProjectObject STRUCT

ObjectData1 ObjectData { }

ProjectData0 ProjectData { }

ObjectEntry0 ObjectEntry { }

ObjectEntry1 ObjectEntry { }

ObjectEntry2 ObjectEntry { }

ObjectEntry3 ObjectEntry { }

ProjectObject ENDS

ProjectObject is given as an example only. Each instanced class must have an object structure to hold its internal state. ProjectObject is an example of the layout of this structure. ProjectObject itself is never defined in a structure definition.

ObjectData1

An instance of the ObjectData structure

ProjectData0

ProjectData is a holder for a particular class member data. It is defined in the project file.

ObjectEntryN

An ObjectEntry structure. Each class needs N+1 ObjectEntry structures in sequence to support N interfaces.

APPENDIX D

GLOBAL VARIABLES

g_ObjectCount

g_ObjectCount DWORD

Contains the count of all objects currently held by the dll.

g_hModule

g_hModule DWORD

A handle to the DLL. The value is the base address of the DLL. The HINSTANCE of a DLL is the same as the HMODULE, so g_hModule can be used in subsequent calls to the GetModuleFileName function and other functions that require a module handle.

g_hHeap

g_hHeap DWORD

Contains the handle to the process heap returned by GetProcessHeap in DllMain.

APPENDIX E MACRO FUNCTIONS

DeclareGuid

DeclareGuid MACRO gName, IID

Macro data function to define a guid from a textequate.

· Defines guid name label as the OFFSET of the guid structure.

· Also defined a named reference to the guid

Parameters

gName

Textequate. Name to use for the GUID.
Will also define "p" & gName with a reference to gName.

reg

Optional parameter. Textequate. Text representation of the guid value. May be left blank if sgName is defined elsewhere in the project.

DeclareVARIANT

DeclareVARIANT MACRO varName, VarType, VarValue

Macro data function to define a variant data structure.

· Defines

· Also defined a named reference to the guid

Parameters

varName

Variable name of the VARIANT being defined.

VarType

Variable type of the VARIANT being defined.

VarValue

Variable value of the VARIANT being defined.

pObjectBase

DWORD pObjectBase MACRO pObject, reg, _offset

Macro code function to re-cast this_ to a reference to the ObjectBase data area.

· Returns cast value in reg.

· Macro code function are always inline.

Parameters

pObject

Object reference, the this_ parameter passed to an class method.

reg

Name of the register to return the cast.

_offsetl

Optional parameter. Specifies an additional offset to be added to the cast.

pObjectData

DWORD pObjectBase MACRO pObject, reg, _offset

Macro code function to re-cast this_ to a reference to the ObjectData data area.

· Returns cast value in reg.

· Macro code function are always inline.

Parameters

pObject

Object reference, the this_ parameter passed to an class method.

reg

Name of the register to return the cast.

_offsetl

Optional parameter. Specifies an additional offset to be added to the cast.

APPENDIX F LIBRARY HELPER PROCEDURES

ComPtrAssign

The ComPtrAssign function assigns one COM pointer to another, performing AddRef and Release as required.

ComPtrAssign PROC

pp:DWORD, // reference of variable to receive a pointer

lp:DWORD // reference to existing COM pointer

Parameters

pp

reference of variable to receive a pointer.

lp

reference to existing COM pointer. May be NULL to release pp

Return Value

None.

See Also

ComQIPtrAssign

The ComQIPtrAssign function performs a QueryInterface on one COM pointer, and assigns the resulting pointer, performing AddRef and Release as required.

ComQIPtrAssign PROC

pp:DWORD, // reference of variable to receive a pointer

lp:DWORD // reference to existing COM pointer

riid:DWORD // reference to existing COM pointer

Parameters

pp

reference of variable to receive a pointer.

lp

reference to existing COM pointer. Must be valid.

riid

reference to an interface identifier

Return Value

None.

See Also

APPENDIX G INITIALIZING LARGE DATA STRUCTURES IN MASM

When working with large structures in MASM, you will run up against a very hard limit: the character count of any given line may not exceed. Logical lines cannot exceed a total of 512 characters.

Period.

Dang.

So, what are you to do when like myself, you wish to implement a vtable consisting of 10 interfaces with 82 function pointers? That is 82 DWORDs alone, and as the string

"DWORD " is 7 characters long, 82 * 7 = 574, so we can't even fit all the type casts inside that structure. What to do?

Easy, you cheat. Finagle. Lie boldface to the compiler.

CoLib structures are arrays of other structures. Small items grouped together to make larger structures. Usually, you can break a large structure into small pieces, and put the pieces into the full structure. That lets you define the structure. Now to initialize an instance of the structure.... cheat!

Structures define how an area of memory is arranged. It is really a compiler directive, not runtime code. So... just make the instance initialization a series of WORDS, BYTES, and DWORDS as you need to duplicate the big structure. Put one nice big label at the top of this data structure.

Say you call the "real" structure "sCOLLUSUS," and this data structure "COLLUSUS." Then you may access its members with:

      mov eax, sCOLLUSUS PTR COLLUSUS.MemberDataItem

Yeah, I know, not very clean. But it does work.

APPENDIX H Creating Registrar Scripts

A registrar script provides data-driven, rather than API-driven, access to the system registry. Data-driven access is typically more efficient since it takes only one or two lines in a script to add a key to the registry.

You will need to generate a registrar script for your COM server. You then make this .rgs script a resource to your project.

The CoLib DllRegisterServer and DllUnregisterServer exports processes your registrar script at run time.

Registrar scripts use the following string literals. Script symbols are case sensitive, The following table describes the string literals used in an ATL Registrar script.

String Literal | Description
ForceRemove | Completely remove the following key (if it exists) and then recreate it.
NoRemove | Do not remove the following key during Unregister.
val | The following <Key Name> is actually a named value.
Delete | Delete the following key during Register.
s | The following value is a string.
d | The following value is a DWORD.
HKCR | HKEY_CLASSES_ROOT
HKCU | HKEY_CURRENT_USER
HKLM | HKEY_LOCAL_MACHINE
HKCC | HKEY_CURRENT_CONFIG
HKDD | HKEY_DYN_DATA
HKU | HKEY_USERS

In a registrar script you define one or more parse trees in your script. A parse tree can add multiple keys and subkeys to the <root key>. In doing so, it keeps a subkeys handle open until the parser has completed parsing all its subkeys. This approach is more efficient than operating on a single key at a time, as seen in the following parse tree example:

HKCR

{

'MyVeryOwnKey'

{

'HasASubKey'

{

'PrettyCool?'

}

}

}

Here, the Registrar initially opens (creates) HKEY_CLASSES_ROOT\MyVeryOwnKey. It then sees that MyVeryOwnKey has a subkey. Rather than close the key to MyVeryOwnKey, the Registrar retains the handle and opens (creates) HasASubKey using this parent handle. (The system registry can be slower when no parent handle is open.) Thus, opening HKEY_CLASSES_ROOT\MyVeryOwnKey and then opening HasASubKey with MyVeryOwnKey as the parent is faster than opening MyVeryOwnKey, closing MyVeryOwnKey, and then opening MyVeryOwnKey\HasASubKey.

Using %MODULE%

It is very often required that a component register the filenamepath where it is stored. This is accomplished with the %MODULE% parameter. CoLib uses this replaceable parameter for the actual location of your server's DLL or EXE. The following example shows how to use the replacement:

'MyGoofyKey' = s '%MODULE%, 1'

will be registered as if it was:

'MyGoofyKey' = s '\myfolder\mydll.dll, 1'

Where "myfolder" is the filepath of the dll, and "mydll.dll" is the file name.

APPENDIX H KNOWN ISSUES

· Plans exist to include support for objects that aggregate other objects, though currently this is unsupported.

· Plans exist to include support for the ISupportErrorInfo interface is still in process.

· The self-registration methods fail catastrophically in NT

APPENDIX I The complete CoLib object map |
 | This map of all the CoLib objects and their interconnections was saved for last, as not to scare away the faint of heart. However, since a picture is still worth about a buck fifty, I felt forced to include it.


MyCom2 and the CoLib

Component Object Library

Copyright © Dec 28, 2000 by Ernest Murphy ernie@surfree.com

For educational use only. All commercial use only by written license.

Re-written Dec 28 2000 for the MASM32 package

Abstract:
--------------------------------------------------------------------------------------------------------------------

A simple COM object is created through a library of reusable functions. The requirements for using this library are discussed.

As this is still a work in progress, there will be minimal explanation given for how the lib works. Release version 1.0. IDispatch is fully working. The requirements for using this library will be given.

--------------------------------------------------------------------------------------------------------------------

In an earlier article I discussed some code that created the fully functional, albeit very simple COM object MyCom. That may now be used as a guide as to what a server must do. The current project creates MyCom2, which while having an identical set of methods inherits from IDispatch and thus supports a dual interface for either early or late binding. One interesting aspect aspect of this addition is the file sizes. The dll grew from 7168 to 12,288 bytes, a total increase of only 5120 bytes for a significant addition of reusable functionality provided by standard library functions.

In writing this library, a simple thought was kept in mind: make it all things to all peoples. Here is a list of the goals of CoLib.lib:

· By including CoLib.inc and CoLib.lib, not one line of code or byte of data shall be added to a project. Library functions are added by the linker.

· Individual library functions may be excluded from use should you wish to customize particular functions without editing the library.

· The 5 DLL exports shall be included and reusable.

· Multiple objects may be incorporated in a single DLL.

· Each object may contain multiple interfaces.

· Aggregation is supported.

· IDispatch is supported.

· Localization for IDispatch is supported

· Registrar scripting is supported using same syntax as the ATL library.

· All type libraries included in the resource are automatically registered and unregistered.

However, CoLib.inc is a work in progress. The following is a list of known issues and "things yet to do."

· The registry exports simply do not work under Windows 2000.

· No method is included to allow a lib object to aggregate other objects. This is planned for a future addition.

Finally, the fun part of this is when we get to writing the server code itself. When originally defined the simple COM server MyCom the .asm contained some 632 lines and 23,539 bytes. MyCom2.asm is just 72 lines of assembly and a remarkable 30 lines total in the .code section. Obviously, much of the overhead has been shifted to the library. Let's start by examining this code in Part II

--------------------------------------------------------------------------------------------------------------------

Here is the complete asm source code for the MyCom2 object. This object is identical to MyCom, except it inherits from IDispatch and thus has a dual interface. Statements defined by the lib are highlighted in blue.

;-------------------------------------------------------------------------------

; MyCom2.dll        copyright 7/15/00 Ernest J Murphy

;

;  sample project to illustrate how to use CoLib, a Component Object Library

;

;-------------------------------------------------------------------------------

.NOLIST  ; keep the list file small, don't add the std libs to it.

.386

.model flat, stdcall

option casemap:none ; case sensitive

;-------------------------------------------------------------------------------

include     \masm32\include\windows.inc

include     \masm32\include\user32.inc

include     \masm32\include\kernel32.inc

include     \masm32\include\advapi32.inc

include     \masm32\include\oleaut32.inc

include     \masm32\include\masm32.inc

include     \masm32\include\ole32.inc

include     \masm32\com\include\oaidl.inc   ; basic COM definitions

include     \masm32\com\include\colib.inc   ; the new library

include     IMyCom2.inc

includelib  \masm32\lib\user32.lib

includelib  \masm32\lib\kernel32.lib

includelib  \masm32\lib\advapi32.lib

includelib  \masm32\lib\oleaut32.lib

includelib  \masm32\lib\ole32.lib

includelib  \masm32\lib\masm32.lib

includelib  \masm32\com\colib\colib.lib     ; the new library

PUBLIC ClassMap                             ; need to export ONLY the ClassMap

;-------------------------------------------------------------------------------

.LISTALL

.data

; describe the classes inside the DLL

ClassMap    ClassItem   { pCLSID_MyCom2, DISPINTERFACE + SUPPLY_TYPE_INFO, \

                          OFFSET MyCom2TypeLibInfo, OFFSET MyCom2IMap,     \

                          CreateMyCom2, NULL, SIZEOF MyCom2Object }

            END_CLASS_MAP

; describe the MyCom2 object's interfaces

MyCom2IMap  InterfaceItem { pIID_IMyCom2,  OFFSET vtableIMyCom2 }

            END_INTERFACE_MAP

; describe the type libraries

MyCom2TypeLibInfo   TypeLibInfo     { pLIBID_MyCom2, 1, 0 }

; describe the MyCom2 object itself (takes 2 steps)

; step 1

MyCom2ObjData   STRUCT                  ; MyCom2 object private data struct

    m_Value         DWORD           0   ; Value (private data member)

MyCom2ObjData   ENDS

; step 2

MyCom2Object    STRUCT

    ObjectData0     ObjectData      { } ; base values

    MyCom2ObjData0  MyCom2ObjData   { } ; custom object data

    ObjectEntry0    ObjectEntry     { } ; delegated Unknown

    ObjectEntry1    ObjectEntry     { } ; IMyCom2

MyCom2Object    ENDS

; fill in the vtable

vtableIMyCom2       IMyCom2     { pvtIDispatch, GetValue, SetValue, RaiseValue }

; define the interface IID's

DeclareGuid IID_IMyCom2

DeclareGuid CLSID_MyCom2

DeclareGuid LIBID_MyCom2

;-------------------------------------------------------------------------------

.code

CreateMyCom2 PROC this_:DWORD

    pObjectData this_, edx  ; cast this_ to object data

    xor eax, eax                                ; get variable

    mov (MyCom2ObjData ptr [edx]).m_Value, eax  ; store new value

    ret                                         ; return S_OK

CreateMyCom2 ENDP

;-------------------------------------------------------------------------------

GetValue PROC this_:DWORD, pval:DWORD   ; GetValue for the IMyCom Interface

    pObjectData this_, edx                      ; cast this_ to object data

    mov eax, (MyCom2ObjData ptr [edx]).m_Value  ; get object data value

    mov ecx, pval                               ; get ptr to variable for return

    mov [ecx], eax                              ; mov value to variable

    xor eax, eax                                ; return S_OK

    ret

GetValue ENDP

;-------------------------------------------------------------------------------

SetValue PROC this_:DWORD, val:DWORD    ; SetValue for the IMyCom Interface

    pObjectData this_, edx  ; cast this_ to object data

    mov eax, val                                ; get variable

    mov (MyCom2ObjData ptr [edx]).m_Value, eax  ; store new value

    xor eax, eax                                ; return S_OK

    ret

SetValue ENDP

;-------------------------------------------------------------------------------

RaiseValue  PROC this_:DWORD, val:DWORD ; RaiseValue for the IMyCom Interface

    pObjectData this_, edx  ; cast this_ to object data

    mov eax, val                                ; get variable

    add (MyCom2ObjData ptr [edx]).m_Value, eax  ; add new value to current value

    xor eax, eax                                ; return S_OK

    ret

RaiseValue ENDP

;-------------------------------------------------------------------------------

end DllMain

--------------------------------------------------------------------------------------------------------------------

Now let's explain what these new library functions do for us:

First, we need to include the lib itself:

include     \masm32\colib\oaidl.inc

include     \masm32\colib\colib.inc

includelib  \masm32\colib\colib.lib     ; new library

It is easier to create an include file to define your interface, as this gives you a running start towards creating the client program to use your new objects:

include IMyCom2.inc

Finally, we make PUBLIC the single ClassMap so the library modules can also use it:

PUBLIC ClassMap ; need to export ONLY the ClassMap

All the library code is included by the linker as it resolves all the symbols, that is all we need do to fully invoke the library functions.

Our class will be defined last (though it appears first) so we can define the class item member names.

When we create a COM object with this lib, it must be to a specific pattern as defined in the lib.

First, we need a place to put the custom data members for the object we create.

MyCom2ObjData STRUCT    ; MyCom object private data struct
Value DWORD 0     ; Value (private data member)
MyCom2ObjData ENDS

This structure is inserted into an object definition. Objects MUST be defined to this pattern, with ClassData followed by the custom data, and finally an ObjectEntry list. This Each interface needs it's own ObjectEntry structure (this is needed to "cast" the "this_" value returned from QueryInterface)

MyCom2Object STRUCT
ClassData0 ClassData     { } ; base values
MyCom2Data MyCom2ObjData { } ; custom object data
ObjectEntry0 ObjectEntry { } ; delegated Unknown
ObjectEntry1 ObjectEntry { } ; IMyCom2
MyCom2Object ENDS

The interface map defines how many interfaces are supported by an object, and links to other information the maps supply. Each interface gets it's own MapItem entry. The structure needs to be defined for each object since you may include any number of interfaces in a particular object.

MyCom2IMap InterfaceItem { pIID_IMyCom2, OFFSET vtableIMyCom2 }
END_INTERFACE_MAP

END_INTERFACE_MAP is a macro to define a NULL interface to mark the end of the InterfaceMap.

Each object needs type library support. It needs a pointer to the library ID, and the major and minor versions of the type lib.

MyCom2TypeLibInfo TypeLibInfo { pLIBID_MyCom2, 1, 0 }

Now we can define the ClassMap. Each object gets a separate ClassItem structure.

ClassMap ClassItem { pCLSID_MyCom2, DISPINTERFACE + SUPPLY_TYPE_INFO, \
OFFSET MyCom2TypeLibInfo, OFFSET MyCom2IMap, \
CreateMyCom2, NULL, SIZEOF MyCom2Object }

END_CLASS_MAP

Similarly to the InterfaceMap, END_CLASS_MAP is a macro to define a NULL interface to mark the end of the ClassMap

The parameters of a ClassItem are defined as follows:

ClassItem1.m_refiid DWORD           ; CLSID for the object
ClassItem1.m_Flags DWORD            ; flags (see below)
ClassItem1.m_pTypeLibInfo DWORD     ; ptr to the TypeLibInfo struct
ClassItem1.m_pIMap DWORD            ; ptr to the MC2InterfaceMap
ClassItem1.m_pConstructor DWORD     ; ptr to the object's constructor function
ClassItem1.m_pDestructor DWORD      ; ptr to the object's destructor function
ClassItem1.m_ObjectSize DWORD       ; SIZEOF the object

m_Flags is the sum of the following:

AGGREGATABLE            Allows aggregation by other objects
DISPINTERFACE           Implements IDispatch through a dual interface
SUPPORT_ERROR_INFO      Supports rich error information for vtable clients
SUPPLY_TYPE_INFO        Type information may be hid with this flag

We create the MyCom2 vtable. Note the use of pvtIDispatch, which is a lib macro to add the addresses of the IUnknown and IDispatch implementations.

vtableIMyCom2 IMyCom2 { pvtIDispatch, GetValue, SetValue, RaiseValue}

We instance the object's 3 required GUIDs here. We also make pointers to these to simplify referencing them (these equated are used above) by using the DeclareGuid macro

DeclareGuid IID_IMyCom2
DeclareGuid CLSID_MyCom2
DeclareGuid LIBID_MyCom2

Similarly to C++, our objects may need custom Constructor and Destructor functions for whatever resources they need. The MyCom constructor simply needs to set the data value to zero.

;------------------------------------------------------------------------------
.code
CreateMyCom2 PROC this_:DWORD
pObjectData this_, edx                          ; cast this_ to object data
xor eax, eax                                    ; get variable
mov (MyCom2ObjData ptr [edx]).m_Value, eax      ; store new value

xor eax, eax                                    ; return S_OK
ret
CreateMyCom2 ENDP

;------------------------------------------------------------------------------

Only the GetValue method is shown for example, as each work in a similar manor. The pObjectData macro is provided from the lib to convert (cast) this_ into a pointer to the object data. The pointer is placed in the register specified to the macro (here it is placed in ecx)

;------------------------------------------------------------------------------
GetValue PROC this_:DWORD, pval:DWORD ; GetValue for the IMyCom Interface
pObjectData this_, edx                          ; cast this_ to object data
mov eax, (MyCom2ObjData ptr [edx]).m_Value      ; get object data value
mov ecx, pval                                   ; get ptr to var for return
mov [ecx], eax                                  ; mov value to variable
xor eax, eax                                    ; return S_OK
r et
GetValue ENDP

;------------------------------------------------------------------------------

That code looks so simple it may be magic. Not really... just the special COM parts of the DLL are stuffed into the lib.

Now's the time to look at the other files required to build our DLL. These are:

· IMyCom2.inc The IMyCom interface definition in asm form

· rsrc.rc standard MASM32 resource file

· MyCom2.IDL Interface Definition File. This is compiled into MyCom.tlb

· MyCom2.def defines the exported functions for the Dynamic Link Library

As before, statements defined by the CoLib library are highlighted in blue.

--------------------------------------------------------------------------------------------------------------------

IMyCom2.inc, the interface definition

; IMyCom2  Interface

;--------------------------------------------------------------------------

;

sIID_IMyCom2       TEXTEQU <{0F8CE5E41H, 01135H, 011D4H, \

                            {0A3H, 024H, 000H, 040H, 0F6H, 0D4H, 087H, 0D9H}}>

sLIBID_MyCom2      TEXTEQU <{0F8CE5E42H, 01135H, 011D4H, \

                            {0A3H, 024H, 000H, 040H, 0F6H, 0D4H, 087H, 0D9H}}>

sCLSID_MyCom2      TEXTEQU <{0F8CE5E43H, 01135H, 011D4H, \

                            {0A3H, 024H, 000H, 040H, 0F6H, 0D4H, 087H, 0D9H}}>

_vtIMyCom2 MACRO CastName:REQ

    ; IDispatch methods

    _vtIDispatch CastName

    ; IMyCom2 methods

    &CastName&_GetValue           comethod2 ?

    &CastName&_SetValue           comethod2 ?

    &CastName&_RaiseValue         comethod2 ?

ENDM

IMyCom2              STRUCT

    _vtIMyCom2 IMyCom2

IMyCom2              ENDS

The interface is defined as per the previous discussions in Creating COM objects in ASM (the orgional MyCom project). It is kept separate from the DLL file so it may be included in other ASM projects.

--------------------------------------------------------------------------------------------------------------------

rsrc.rc, the resource file

// get standard resources

#include <c:\masm32\include\colib\coserver.rc>

// type libraries here,

IDD_TypeLib         TypeLib         MyCom2.tlb

// add subsequent type libraries as such:

//  IDD_TypeLib + 1     TypeLib         MyCom3.tlb

//  IDD_TypeLib + 2     TypeLib         MyCom4.tlb

// the one and only registrar script

IDD_RGS_SCRIPT  REGISTRY    C:\masm32\MyCom2\MyCom2.rgs

// add additional resources here starting at ID number 300

This should be simple to duplicate. All you are specifying is the type library file and the resistrar script file. Should you have multiple objects, just keep adding the type libraries as the comment suggests. CoLib will register every type lib in the list that appear at the IDD_TypeLib resource number and on. There can only be one registrar file, so if you have multiple objects, you must hand edit these scripts into a single script.

MyCom.IDL Interface Definition File

--------------------------------------------------------------------------------------------------------------------

MyCom2.idl, the interface definition file

I will not pretend that I understand Interface Definition Language or syntax. All I can state is the following code works. I have Jeremy of Collake Software (http://www.collakesoftware.com/main.stm) to thank for the outline of this file (cause I stole mine from him).

// MyCom2.idl : IDL source for MyCom2.ocx (or dll)

//

import "oaidl.idl";

import "ocidl.idl";

[

   uuid(F8CE5E42-1135-11d4-A324-0040F6D487D9),

   helpstring("MyCom2 1.0 Type Library"),

   version(1.0)

]

library MyCom2Lib

{

   importlib("stdole32.tlb");

   [

         uuid(F8CE5E41-1135-11d4-A324-0040F6D487D9),

         dual,

         helpstring("IMyCom2 Dispatch Interface")

   ]

   interface IMyCom2 : IDispatch

   {

         [propget, id(0),
helpstring("property Value")] HRESULT Value([out, retval] long
*pVal);

         [propput, id(0), helpstring("property Value")] HRESULT Value([in] long newVal);

         [id(1), helpstring("method Raise")] HRESULT Raise([in] long AddVal);

   }

   [

         uuid(F8CE5E43-1135-11d4-A324-0040F6D487D9),

         helpstring("MyCom2 Class")

   ]

   coclass MyCom2

   {

         [default] interface IMyCom2;

   }

};

There isn't much to say here, basically you *MAY* use MSVC to write this file for you as described in Creating COM objects in ASM. However, do check that IDispatch is working correctly. The CoLib lib uses the type library here to perform the IDispatch functions, and a bad type lib means no IDispatch.

--------------------------------------------------------------------------------------------------------------------

MyCom.rgs Resource Script File

If you use MSVC to define your .idl file, you also get the MyCom2.rgs registrar file. The registrar file looks like so:

HKCR

{

   MyCom.MyCom2.1 = s 'MyCom2 Class'

   {

         CLSID = s '{F8CE5E43-1135-11d4-A324-0040F6D487D9}'

   }

   MyCom.MyCom2 = s 'MyCom2 Class'

   {

         CLSID = s '{F8CE5E43-1135-11d4-A324-0040F6D487D9}'

         CurVer = s 'MyCom.MyCom2.1'

   }

   NoRemove CLSID

   {

         ForceRemove {F8CE5E43-1135-11d4-A324-0040F6D487D9} = s 'MyCom2 Class'

         {

               ProgID = s 'MyCom.MyCom2.1'

               VersionIndependentProgID = s 'MyCom.MyCom2'

               ForceRemove 'Programmable'

               InprocServer32 = s '%MODULE%'

               {

                     val ThreadingModel = s 'Apartment'

               }

               'TypeLib' = s '{F8CE5E42-1135-11d4-A324-0040F6D487D9}'

         }

   }

}

This file follows the registrar script format as defined in MSDN. Keep in mind capitalization is important here.

--------------------------------------------------------------------------------------------------------------------

MyCom2.def exported dll functions

; MyCom2.def : Declares the module parameters.

LIBRARY "MyCom2.dll"

EXPORTS
DllCanUnloadNow @1 PRIVATE
DllGetClassObject @2 PRIVATE
DllRegisterServer @3 PRIVATE
DllUnregisterServer @4 PRIVATE

This file is mostly boilerplate. All you need define is the LIBRARY name, which is optional as you really do not need the .lib file for a COM dll. The export names are always the same. You do need to name the file itself so a standard .dll compile and link bat file may find it.

CONCLUSION

--------------------------------------------------------------------------------------------------------------------


Inside coserver

Adding a Script Engine to Assembly Applications

Copyright © 2000 by Ernest Murphy ernie@surfree.com

For educational use only. All commercial use only by written license.

ABSTRACT

Scripting engines are a fast, safe, and inexpensive way to add macro capability to an application. Microsoft supplies both the Visual Basic script engine and the Java script engines free of charge. These are the same engines that run inside Internet Explorer, and they can be used to good advantage.

A simple test application will be demonstrated.

INTRODUCTION

The script engines are packaged as ActiveX components in dynamic link libraries, vbscript.dll and jscript.dll. You probably have both already on your computer. They are quite simple to use, with the minor exception that they work through COM interfaces. (Well, that's minor to me anyway).

The script engine themselves are written to a coherent pattern, so that by simply invoking the coclass for either the VB or the Java script engine, the same application can change it's scripting language. This means we can change languages by simple changing a single constant in our application.

What's a Script Engine Anyway?

Microsoft is very concerned with leveraging their code. When it came time to provide scripting to the Internet Explorer project, they did a very good thing. They packaged the Visual Basic and the Java script as reusable ActiveX components. Then they went one step further: they documented how they worked and gave them away free.

Literally Free. Here is a direct quite from the Microsoft Scripting Technology page: "Licensing the JScript & VBScript binary for use in your application is very easy Simply acknowledge Microsoft in the About box of your application. There is no charge for this license."

These scripting languages are well documented and widely used. You would not be forcing your users to learn new tricks just to script your application, making for a wider user acceptance.

How to Connect a Script Engine

Any application that supports the IActiveScriptSite interface may use a script engine. If a visual interface is supported, IActiveSiteWindow should also be implemented.

A script engine connects in to an application in 8 quick and quite simple steps:

1. Create the Host Document: This is com-speak for start your program application.

2. Create the ActiveX Scripting Engine: More com-speak, for you only create the object from out of the dll with a call to CoCreateInstance for the IActiveScript interface. Once you have this interface, QueryInterface it for IActiveScriptParse.

3. Add ScriptSite Interface: So the engine can find your application, give it your IActiveScriptSite interface with a call to IActiveScript.SetScriptSite

4. Initialize the Engine: Give the engine a chance to initialize itself by calling IActiveScriptParse.InitNew

5. Add Object References: Should you wish the script to control your application's objects, pass it the name of your top level object with IActiveScript.AddNamedItem. The engine will reply with a request for the object's IUnknown through IActiveScriptSite.GetItemInfo.

6. Load The Script: Your application needs to have a script to run. Scripts are simply text files containing the source code. Scripting obviously uses run-time compilation. The IActiveScriptParse.

7. Run the Script: Finally, begin running the script with a call to IActiveScript.SetScriptState with SCRIPTSTATE_CONNECTED.

8. Supply your hwnd: If your script needs a window (for example, to display a message box), the engine will need the hwnd of your application. It will request this with a call to your applications IActiveSiteWindow.GetWindow method.

The script will now run, and may control your application through the top level object.

Mixing COM with Windows

COM and Windows have two different ways of accessing external data and events. One must understand first how Windows fires it's events so we can execute the COM methods in the proper order. In this case, we only need to do a few things:

1. Initialize: We load the script engine in the WM_CREATE message handler for the applications main window.

2. Clean-Up: We do all our clean up (releasing interfaces to unload the script engine) in the WM_DESTROY message handler.

3. Running The Script: For this demonstration, we put a big command button on the main window captioned "RUN SCRIPT." The handlers for this message loads and runs the script.

The initialization in WM_CREATE is helped along with two procedures, LoadScript and RunScript.

LoadScript looks for a file named "MyScript.vbs," converts it to Unicode and returns a pointer to this string. It also coppies the script directly to the edit box with a SendMessage, so you can see the script being run.

RunScript just keeps all the COM methods to start the script engine in one place for clarity.

You will also need a pair of objects with your application, ActiveScriptSiteClass for the IScriptSite and IScriptSiteWindow interfaces, and ScriptTextClass for the Application object The Application object is the one the script can take charge of to make changes in your application. Thanks to the colib, these objects are simple to define.

Perhaps surprisingly, the IScriptSite and IScriptSiteWindow interfaces are not dispinterfaces. They do not support IDispatch. This is because the script engine is designed to work with these pre-defined interfaces, and it has full prior knowledge of then, thus can early bind.

However, the IScriptText interface of ScriptTextClass MUST be a dispinterface, as the script engine has zero prior knowledge of it's composition. Thus, you will need to define an .IDL file and compile a type library for it, as colib needs the type library to run IDispatch. Additionally, the script engine is allowed to ask for an ITypeInfo interface from us, and again, that means having this library.

Those interested in the class structure definitions for these classes are invited to view the source. This is the same as presented before (See the MyCom2 and AsmCtrl tutorials), with the exception of we need not produce a ClassMap.

The ClassMap is only needed for DllGetClassObject to search for supported classes. Here, we simply pass a direct reference of the class to the colib method AllocObject, instead of having DllGetClassObject do this for us. We can do this because these interfaces are no longer belong to the client, they are part of the application, and the application sets the rules on when objects are instanced.

Implementing the Class Methods

A surprisingly large number of the interface members we need implement just return S_OK or E_NOTIMPLEMENTED and are quite trivial. These will not be discussed.

The ScriptTextClass was designed to allow the script to write a string to the edit control on the main window. Thus, the sole method of this interface is SetText.

The SetText method will the first clear the control of all text with SendMessage EM_SETSEL to select all text, followed by a SendMessage WM_CLEAR. The text from the script is passed to us in a BSTR. BSTRs are Unicode strings, so a buffer for the ASCII must be allocated, then WideCharToMultiByte called to perform the conversion. After this, it is simple to SendMessage WM_SETTEXT to change the text. Finally, the buffer we allocated and also the BSTR are released.

IActiveScriptSite has only a single non-trivial method, GetItemInfo, with of course the longest list of parameters. This method is used to link up an object name string with it's interfaces (as determined by the application).

Since we only support a single named object, the BSTR passed in is compared to our object name. In keeping with Microsoft naming conventions, I named this top object "Application" Rather grandiose, but in keeping with standards.

The engine may ask us for either an IUnknown pointer, or an ITypeInfo, as determined by the dwReturnMask flags. If we are asked for the "Application" object the flags are checked, and the appropriate pointers returned. Note that IDispatch has a GetTypeInfo method. This is used to supply the ITypeInfo pointer.

IActiveScriptSiteWindow is required if we wish the script to be able to be 'visual,' meaning display message boxes and such. Thus, the GetWindow method is filled in to return the hwnd of the main application window.

Really, that was just 3 procedures we needed to implement to get the script engine in charge of the application.

Building the ScripTxt Sample

The sample application may be build using the MASM32 package (cuttently version 6) with Service Pack 1 installed. The SP is needed for the COM extensions.

Two new COM include files are contained in the sample: activscp.inc and msscript.inc. These should be moved to /masm32/COM/include/.

ScripTxt.asm will then build properly from Quick Editor using the BUILD ALL setting.

Writing Scripts to Control the Application

You now have the full power of the VBScript engine to work for you. It is quite a powerful language.

Additionally, we have passed the engine a reference to our ScriptTextClass object, so the script may control the edit control text directly (as long as all it needs do is replace it all). Note in a REAL application more methods would have been added to the ScriptSiteClass to give it more 'scriptability.'

The ScriptSiteClass instance, named "Application" is passed to the script. Thus in VBScript we may access the edit box as so:

      Application.SetText  = "Some text here."

      MyVariable = "Some other text"

      Application.SetText = MyVariable

In the script sample provided, the script is used to do a simple calculation and display the result on the edit control. Also, some text input boxes are used to let you pick what the edit control should say, so you see the script is really doing as described.

Conclusion

Scripting engines provide a simple way to add a very powerful macro language to your application. The only cost is "About screen" credit to the company we all love to hate.


Inside coserver

Standard for COM in MASM32

ABSTRACT

The basis and rational for Component Object Programming 'include' files as defined in these files is explained.

INTRODUCTION

Attempting to perform COM operations without any include files can be a frustrating proposition. Several people have made some attempts, with some success, but each are working to their own "standard" as to COM information. I have seen some of this work when published, but found it lacking in some respects.

Many months of efforts, and several sometimes major revision has gone into these files. Public comments were sought and included where applicable.

The goal of this document is to get new and existing COM in ASM players to 'play by the same rules,' such that any new file may be added as is to this hopefully growing library of include files and definitions.

Include File Features

Any .inc file should have the following features:

1) The MASM32 convention of loose typedef will be continued. This means parameters will be defined as their base type, typically a DWORD.

2) The information should not create any code lines at all. It will merely define information for the mail application. Should a file need to make functions, these should be in the form of macros so they are not expanded to code unless explicitly instructed to.

3) Structures shall be defined to align with their "C" prototypes. Structure curly brackets { } are preferred over angle brackets < >.

4) The GUID structure itself is defined in windows.inc. This definition is correct. GUID values shall be defined through a textequ so they do not directly generate any code. See the following sections on GUIDS.

5) Interfaces are defined in a two-step process where first a general macro is declared to create a general Interface structure, then the structure itself is defined with the Interface name as name decoration on the method name. This both makes methods immune to namespace duplication (necessary for method polymorphism) and allows Interface structure definitions to be 'inherited' in succeeding Interfaces. See the following section on Interfaces.

6) COM Interface method calls will be accomplished with the coinvoke Macro. This macro uses the Interface definition as given to de-reference the object pointer to the function table pointer, and then generate the offset into the function table pointer list and call that function. Simple parameter count and size checking is also performed at compile time. See

the following section on the coinvoke Macro.

GUIDS

GUID values shall be defined through a textequ so they do not directly generate any code. This equate shall have a label consisting of the interface name with a "s" (for string) prefix.

EXAMPLE:

sIID_IUnknown  TEXTEQU  <{000000000H, 00000H, 00000H, \
                            {0C0H, 000H, 000H, 000H, 000H, 000H, 000H, 046H}}

This can be employed as such:

IID_IUnknown   GUID    sIID_IUnknown

The DeclareGuid Macro is provided as an aid to defining GUIDs It has the form:

DeclareGuid GuidName, [<guid value>]

INTERFACES

Due to the MASM32 prototype convention of loose type checking in invokes, all that is really checked at compile time is a simple count of parameters. This fact can extremely simplify our definitions of an interface, as the same small set of function prototypes can be used over and over. Interface structure members (methods) will be defined with the interface name plus an underscore as name decoration.

With the sole exception of IUnknown, all interfaces must inherit from another interface. It would be convenient to have some sort of macro provide us this function. Thus, the manor interfaces are created from a macro will allow that macro to be re-used (thus "inherited) in further definitions.

The following definitions are first established for all interfaces to use:

comethod1Proto   typedef proto :DWORD

comethod2Proto   typedef proto :DWORD, :DWORD

comethod3Proto   typedef proto :DWORD, :DWORD, :DWORD

comethod4Proto   typedef proto :DWORD, :DWORD, :DWORD, :DWORD

comethod4Proto   typedef proto :DWORD, :DWORD, :DWORD, :DWORD, :DWORD

comethod1        typedef ptr comethod1Proto

comethod2        typedef ptr comethod2Proto

comethod3        typedef ptr comethod3Proto

comethod4        typedef ptr comethod4Proto

comethod5        typedef ptr comethod4Proto

Actually, these definitions are carried out for 15 parameters. I arbitrarily chose this number, should a function come along with more then 15 parameters, of course this table should be extended. This may seem to be strongly in disagreement with my previous writings. However, the same coinvoke macro will handle this. All I have done was eliminate a ton of proto names that never got used by the compiler.

There are a few methods that do not take DWORD size parameters (most notable, the IDispatch Interface). These methods are handled on a case-by-case basis, at the place in the include file where that differing method is declared.

Let us look at some familiar interfaces generated with these methods:

EXAMPLE 1 The IUnknown Interface:
IUnknown is the basic interface, all others inherit from this. Function prototypes are defined as above. The form of the vtable structure is defined in a macro, then this macro is used to create the structure itself.

_vtIUnknown MACRO CastName:REQ

    ; IUnknown methods

    &CastName&_QueryInterface comethod3 ?

    &CastName&_AddRef         comethod1 ?

    &CastName&_Release        comethod1 ?

ENDM

IUnknown                        STRUCT

    _vtIUnknown IUnknown

IUnknown                        ENDS

This will expand to the following:

IUnknown                        STRUCT

    IUnknown_QueryInterface comethod3 ?

    IUnknown_AddRef         comethod1 ?

    IUnknown_Release        comethod1 ?

IUnknown                        ENDS

The macro is defined with a leading underscore, since the "vt" prefix is handy to leave undefined when it comes time to instance a vtable.

Thus, one may wish to provide an instance of the vtable as such:

      .data

vtIUnknown    _vtIUnknown  {MyQueryInterface, MyAddRef, MyRelease}

Method names are defined with the interface name added on as name decoration. Different Interfaces may have methods with the same name (that is simple polymorphism), but without this name decoration the MASM compile cannot tell them apart.

(Note that it is legal for two different interfaces may have the same name, same method names, but differing parameters. That is valid (only the IID GUID's need be different), but bad coding practice, so we'll ignore that condition with just this warning here. This standard cannot tell those interfaces apart, and some workaround must be sought.)

EXAMPLE 2 The IClassFactory Interface
IClassFactory inherits from IUnknown. It begins with the IUnknown methods, then adds it's own 2 methods.

_vtIClassFactory MACRO CastName:REQ

    ; IUnknown methods

    _vtIUnknown CastName

    ; IClassFactory methods

    &CastName&_CreateInstance comethod4 ?

    &CastName&_LockServer     comethod2 ?

ENDM

IClassFactory                   STRUCT

    _vtIClassFactory IClassFactory

IClassFactory                   ENDS

This will expand to the following:

IClassFactory                   STRUCT

    IClassFactory_QueryInterface comethod3 ?

    IClassFactory_AddRef         comethod1 ?

    IClassFactory_Release        comethod1 ?

    IClassFactory_CreateInstance comethod4 ?

    IClassFactory_LockServer     comethod2 ?

IClassFactory                   ENDS

coinvoke, a MACRO to perform COM Interface Methods

We can simplify a COM invoke further with a macro:

;---------------------------------------------------------------------

; coinvoke MACRO

;

; invokes an abritrary COM interface

;

; revised 12/29/00 to check for edx as a param and force compilation

;                 error(thanks to Andy Car for a how-to suggestion)

; revised 7/18/00 to pass pointer in edx not eax to avoid confusion

;  with parmas passed with ADDR (Jeremy Collake's excellent suggestion)

; revised 5/4/00 for member function name decoration

;

; pInterface    pointer to a specific interface instance

; Interface     the Interface's struct typedef

; Function      which function or method of the interface to perform

; args          all required arguments

;                   (type, kind and count determined by the function)

;

coinvoke MACRO pInterface:REQ, Interface:REQ, Function:REQ, args:VARARG

    LOCAL istatement, arg

    FOR arg, <args>     ;; run thru args to see if edx is lurking in there

        IFIDNI <&arg>, <edx>

            .ERR <edx is not allowed as a coinvoke parameter>

        ENDIF

    ENDM

    istatement CATSTR <invoke (Interface PTR[edx]).&Interface>,<_>,<&Function, pInterface>

    IFNB <args>     ;; add the list of parameter arguments if any

        istatement CATSTR istatement, <, >, <&args>

    ENDIF

    mov edx, pInterface

    mov edx, [edx]

    istatement

ENDM

;---------------------------------------------------------------------

Thus, the same QueryInterface method as before can be invoked in a single line:

coinvoke ppv ,IUnknown, QueryInterface, ADDR IID_SomeOtherInterface,

ADDR ppnew

Note that now the name decoration is done for us by the macro.

Testing HRESULTS

The return parameter for every COM call is an hResult, a 4 byte return value in eax. It is used to signal success or failure. Since the most significant digit is used to indicate failure, you can test the result with a simple:

.IF !SIGN?
; function passed
.ELSE
; function failed
.ENDIF

Again, this can be simplified with some more simple macros:

.IF SUCCEEDED     ; TRUE if SIGN bit not set
.IF FAILED        ; TRUE is SIGN bit set

And, for the truly code-frugal person who doesn't with to explicitly test eax, these macros will be useful:

.IF_SUCCEEDED     ; TRUE if eax >= 0

      .IF_FAILED        ; TRUE if eax < 0

These macros also perform the test for you.

Conclusion

That's about all you need to fully invoke and use interfaces from COM objects from assembly. These techniques work with any COM or activeX object.


Inside coserver

UsingCoLib to Build a Visual Control (.OCX)

Copyright © Dec 30, 2000 by Ernest Murphy ernie@surfree.com

For educational use only. All commercial use only by written license.

Sample code for this tutorial is available at ...\COM\examples\AsmCtrl\

A VB sample client is available at ...\COM\examples\AsmCtrl\testapp

The Build OCX setting used for Quick Editor is at ...\COM\BIN\BLDOCX.BAT

ABSTRACT

A visual control is demonstrated. A Visual Basic project is supplied to test this object.

INTRODUCTION

Visual controls are a very useful area to be explored for COM in assembly. Currently. the popular methods of making these controls includes MFC and ATL in C++, VB, Java, and a few other minor players. With the possible exception of ATL, these methods lead to large bloated controls with huge runtimes.

The sample control resides in a 24,576 byte dynamic link library, and of that 9,009 are simply resources (bitmaps and the typelib). No further runtimes are required.

Description of the Control

The control described here is a fairly straightforward C++ to ASM port of the control designed and built by Sean Baxter as is available at: http://ript.net/~spec/captcom/journal3/

Sean does an excellent job of defining a control using just C++ (very light dependence on the ATL libraries) and explaining how a minimum set of interfaces can work in conjunction with the client site interface of Visual Basic to create a custom visual control.

I strongly suggest you read his excellent 3 part tutorial to understand how these interfaces work. You might also note I royally simplified Sean's custom control properties such that the PAINT methods needed in the asm control are to simply display the control name. I also eliminated his property pages. However, I did implement Property Categories

AsmCtrl also includes a Value property, Fore and BackColor properties, and implements ICategorizeProperties

Controls as Visual Basic Components

A Control is based on the same principles as any other in-process (dll) server. However, it does implement several special interfaces, methods, and resources to make it as simple to use for the VB developer as possible.

For those of you who see VB as far too simplistic a language to be of any note, and all its users 'lamers,' let me suggest an alternative definition for you:

VB users are clients. VB users will pay big bucks to buy your super duper super fast gizmo coded in 100% ASM.

VB users who cannot buy wonderful components will be forced to become crack distributers to the detriment of all of society. It is our civic duty to supply them with simple to use, functional components.

(Please note a written license is required to use CoLib.lib or any of these files in a commercial product. Have your girl call my girl, we'll do lunch and work it out.)

Visual controls have the following features to make them even easier to use:

· ControlBox bitmap: This is the icon one uses to select the control. It is typically 16x15 pixels in size, 16 colors, and the lower left corner pixel is used to indicate a transparent color.

· AboutBox: It is convienent to let the used know who made each component. By giving it an AboutBox method, you get the opportunity to sign your work, and also to pass on some hints to the user (such as a web page for further info).

· Properties: Values saved in a control may be changed through a pair of Set and Get procedures. VB hides this complication from users, and uses the concept of Properties to hold values. In the VB IDE, those Properties that should be set at runtime are displayed in the Object Browser window. The control uses facilities offered by VB to save these properties, then retrieve them at runtime.

ICatagorizeProperties

This interface only makes sense in the Visual Basic IDE. VB is designed to make things as simple as possible for the programmer.

Property Catagories are groups of similar properties. Most standard properties, such as Width, Height, About, Tag, Index, and such, have pre-identified catagories.

When defining a new custom control, you need a to specify the catagory of the new properties. The ICategorizeProperties interface has this job.

The dispid (dispatch ID number) of a property is passed to ICategorizeProperties_MapPropertyToCategory, and if the property is mapped, that Catagory number is returned. ICategorizeProperties_GetCategoryName returns this Catagory name for the given number.

VB does this behind the scenes as it loads your control.

Conclusion

This article does not deserve the title 'tutorial,' as it is only the briefest of descriptions for this control. It will have to serve for now, until such time I finish a truly useful control, with events, error handling, and so on.


COM

ASM COM

La littérature sur le sujet est abondante.Vous trouverez dans cette page quelques points précis facilitant leur utilisation.

INTRODUCTION et THEORIE:

Pratique:

Le COM (Components Object Model) est une méthode de regroupement de fonctions contenues dans des DLL.Mieux vaut être familiarisé avec l'adressage des DLL , si on veut comprendre le fonctionnement des interfaces.C'est l'objet de cette introduction.La méthode d'appel est spécial.Les adresses des fonctions sont regroupées dans des structures en mémoire,appelé table des vecteurs.En dehors de cette structure,on en trouve une autre contenant,en particulier,le pointeur sur la table des vecteurs.Ce pointeur est appelé communément ppv et il faut toujours une api pour avoir sa valeur.CoCreateInstance en est une parmi d'autres.Seule la documentation de l'interface (ensemble Table des vecteurs et pointeurs) peut donner l'API à utiliser.

Implémentation d'une table de vecteurs,ou table d'adresse de fonctions

Table des vecteurs :déclaration

VectTabl STRUCT

QueryInterface dd ? Les trois premières constitue Iunknown

AddRef dd ? réservées aux dialogues avec le système

Release dd ?

;--- fin de l'interface IUnknown -----------

FausseCom1 dd ?

FausseCom2 dd ?

FausseCom3 dd ?

VectTabl ENDS

Toute interface dérive et implémente IUnknown

Dans le segment des data , on va remplir la structure avec les adresses de PROC
.data
TableVecteurs VectTabl <offset Fonct1,offset Fonct2,offset Fonct3>

Définition de la structure d'interface.

    ComData Struct
    ppv DWORD ? ;va contenir l'adresse de la table des vecteurs,toujours en premier
    count DWORD ? ;pour compter le nombre d'initialisations de l'interface
    ;autres datas ........;On peut rajouter ce que l'on veut içi,données de l'interface
    Comdata ends

Interface ComData <offset TableVecteurs,0,,>

Définition de la valeur de This

.code

    lea ecx,Interface ;ecx contient This,This est un pointeur sur la structure Interface

    Si une fonction renvoie This : mov ecx,this

    retrouver la valeur du pointeur sur TableVecteurs

    mov eax,[This] ;placer le contenu de l'adresse indiqué dans eax

    appeler la premiere fonction directement:

      push parm1

      push parm..

      call [eax]

    appeler la deuxieme fonction directement:

      push parm1

      push parm..

      push parmN

      call [eax+4] ;+4 dword contenant l'adresse de la fonction

Former l'appel

Conventions d'appel COM:
CoCreateInstance nous fournit l'adresse de la structure Interface,c'est a dire THIS,dans un dword en data
Pour retrouver l'adresse de la table des vecteurs , il nous faut procéder a une petite manipulation:

.data
Out_CoCreateInstance dd 0 ;THIS est placé içi par CoCreateInstance
.code
mov edx,Out_CoCreateInstance ;on récupère l'adresse de l'interface dans edx,edx = THIS
mov edx,[edx] ;on charge le pointeur sur la table des vecteurs,edx pointe sur VectTabl
call (edx + index) ;pointeur sur VectTabl + (Index de fonction * 4) ,l'index démarre a zéro
;l'index ,multiple de 4,permet de choisir la fonction

THIS est passé systématiquement en premier paramètre a toutes les fonctions COM et ajoute un dword a tous les PROTO

FonctionCom PROTO THIS:DWORD + (Paramètres normaux)
Le c,c++... masque ce paramètre,et avec masm,il faut systématiquement rajouté un argument a tous les protos COM définis dans les inclus du SDK
Nouvelle Méthode d'appel

Utilisation de Invoke pour accéder aux fonctions COM de la DLL

La syntaxe de l'appel se complique dès lors que l'on veut utiliser invoke.Invoke necessite l'existence d'un proto définissant les paramètres passés au PROC.

Pour une fonction nommée Fonction PROTO THIS:DWORD ,la fonction est sans paramètre
La déclaration de la fonction étant dans une structure,on utilise la formule suivante

FUNCPROTO TYPEDEF PROTO :DWORD

FUNCPTR TYPEDEF PTR FUNCPROTO

    Une fois ppv rempli par les lignes précédentes,on souhaite appeler une des fonctions du tableau

    mov eax,ppv ;chargé l'adresse de la variable AdrVtable dans eax

mov eax,[eax] ;prendre le contenu de cette adresse (offset TableVecteur)

mov eax,[eax].VectTabl.FausseCom3 ;récupéré l'adresse fausse fonction COM

call eax
OU

; adressage indirect

mov eax,ppv ;chargé l'adresse de la variable AdrVtable dans eax

mov eax,[eax] ;prendre le contenu de cette adresse (offset TableVecteur)

call dword ptr [eax].VectTabl.FausseCom3

L'adressage indirect se réfère a la structure précédemment décrite et calcule le décalage nécessaire pour charger l'adresse finale.

Pour utiliser Invoke en mode d'adressage indirecte,il faut procéder de la manière suivante

comethod0Proto typedef proto

comethod1Proto typedef proto :DWORD

comethod2Proto typedef proto :DWORD, :DWORD

comethod3Proto typedef proto :DWORD, :DWORD, :DWORD

comethod0 typedef ptr comethod0Proto

comethod1 typedef ptr comethod1Proto

comethod2 typedef ptr comethod2Proto

comethod3 typedef ptr comethod3Proto

VectTabl STRUCT

QueryInterface comethod3 ?

AddRef comethod1 ?

Release comethod1 ?

FausseCom1 comethod0 ?

FausseCom2 comethod0 ?

FausseCom3 comethod0 ?

VectTabl ENDS

et les instructions d'adressage indirecte précédentes deviennent
mov eax,ppv ;chargé l'adresse de la variable AdrVtable dans eax

mov eax,[eax] ;prendre le contenu de cette adresse (offset TableVecteur)

invoke (VectTabl PTR[eax]).FausseCom3

Invoke en mode indirect exige des [] pour fonctionner ,ce qui signifie faire un appel en prenant le contenu de l'adresse + ...

.

Obtenir un Pointeur de Pointeur de Vecteurs ,CoCreateInstance

On veut réutiliser une interface existante.La table des registres (regedit pour voir son contenu) contient d'importantes clés permettant leur utilisation.

HKEY_CLASSES_ROOT\CLSID le clsid est un guid désignant de manière unique une coclass.
une coclass est un ensemble d'interfaces utilisables lorsque l'on connait la valeur de son clsid.La coclass est défini uniquement dans les typelib (Oleview)

HKEY_CLASSES_ROOT\Interface On trouve içi des guid (IID_..) désignant de manière unique des interfaces.Les interfaces sont complètement décrites dans les SDK (header's files .h).MIDL produit ces fichiers à partir des typelib

HKEY_CLASSES_ROOT\TypeLib On trouve içi des GUID désignant de manière unique les typelib.Les typelib sont des fichiers textes pouvant donner toutes les informations nécessaires à l'utilisation.Leur particularité est d'être compilable par MIDL.Une fois compilé,on peut les trouver n'importe ou,y compris dans une ressource.La clé de registre donne leur emplacement/

La clé de registre CLSID donne souvent deux moyens de se connecter à une interface.Le clsid lui même ou le PROGID.

Par un nom de programme identifié

on peut dire aussi ,obtenir l'adresse de AdrVtable

Il faut avoir préablement déclaré l'utilisation de COM avec CoInitialize

il faut remplir les structures GUID (défini dans windows.inc) avant d'appeler CoCreateInstance .
CoCreateInstance retrouve la dll spécifiée par les deux clés de registre sIID et IID.La dll en question est déja chargée puisque faisant partie du système.

GUID STRUCT

Data1 dd ?

Data2 dw ?

Data3 dw ?

Data4 db 8 dup(?)

GUID ENDS

Les valeurs de remplissage de GUID sont contenus dans la base de registre

Pour l'interface, la valeur de la clé permet de retrouver la définition guid de l'interface

HKEY_LOCAL_MACHINE\Software\CLASSES\Interface\{000214EE-0000-0000-C000-000000000046} Valeur IShellLinkA

La chaine correspante est

sIID_IShellLink TEXTEQU <{0000214EEH,0,0,{0C0H, 0, 0, 0, 0, 0, 0, 046H }}>

Pour l'objet .lnk , il faut lire la clé .lnk qui donne lnkfile ,puis prendre la valeur de la clé CLSID

HKEY_LOCAL_MACHINE\Software\CLASSES\lnkfile\CLSID Valeur {00021401-0000-0000-C000-000000000046}

sCLSID_ShellLink TEXTEQU <{000021401H, 0, 0,{0C0H, 0, 0, 0, 0, 0, 0, 046H }}>

Il existe deux maniéres de remplir les structures

soit directement

IID_IShellLink GUID <0000214EEH, 0, 0,< 0C0H, 0, 0, 0, 0, 0, 0, 046H >>

soit en affectant une valeur texte

IID_IShellLink GUID sIID_IShellLink ;texte

l'avantage de la deuxième méthode , est la possibilité de stocker sIID_IShellLink dans un header .h

HKEY_CLASSES_ROOT est équivalent a HKEY_LOCAL MACHINE\SOFTWARE\Classes

la syntaxe pour les chaines de textes est légérement différente de la définition de la structure

Les .inc (masm32\com\include ) contiennent egalement ces valeurs

Appel

invoke CoCreateInstance, ADDR CLSID_, NULL,CLSCTX_INPROC_SERVER, \

ADDR IID_IShellLink, ADDR Pinterface

CLSCTX_INPROC_SERVER

Traduit, la fonction appelée se comporte comme un Proc et non comme un programme.

CoCreateInstance renvoi l'adresse d'un pointeur sur le tableau d'adresses.Pour appeler une fonction,il faut rajouter Pinterface comme argument de la fonction.ARGUMENTS + 1

mov edx, Pinterface

mov edx, [edx]

invoke (IShellLink PTR[edx]).IShellLink_GetPath, Pinterface, \

addr NomComplet,sizeof NomComplet,addr Raccourci,SLGP_UNCPRIORITY

Les fichiers d'aides décrivent les fonctions utilisables avec l'interface.L'appel de fonction se faisant par l'intermédiaire d'un Tableau d'adresses , il est nécessaire d'avoir la définition de l'interface (structure de DWORD).

exemple:

Iunknown Struct

QueryInterface comethod3 ?

AddRef comethod1 ?

Release comethod1 ?

Iunknown ends

Pour éviter les confusions , on rajoute le nom de l'interface au nom de la fonction

IShellLink::GetArguments forme C de l'écriture

IShellLink_GetArguments forme MASM,a l'appel Pinterface argument en +

Lorsqu'il est présisé que l'application utilise une autre interface , celle ci est accessible par QueryInterface

Pour passer certains paramètres chaines de caracteres , il faut préalablement traduire la chaine en UNICODE avec MultiByteToWideChar.

Exemple: Telecharger ExplTxt.zip

Dans ExplTxt.asm , aller 'LireRaccourci PROC hWndParent:DWORD',pour voir la partie com.

Généralisation:

A chaque nouvelle demande de fonction com,on doit

1) retrouver le pointeur d'interface avec CoCreateInstance ou autre méthode particulière a l'interface

2) Ajouter le nom de l'interface au nom de fonction , pour éviter la confusion entre des fonctions de même nom pour deux interfaces différentes.C'est le role de la macro coinvoke.

Les fonctions de l'interface IUnknown étant communes a toutes les interfaces,on voit l'intérêt de procéder ainsi.

3)Former l'appel

Comment réduire un appel COM a un simple appel API

On a vu que toute la difficulté d'utilisation tient a la forme d'adressage,en adoptant quelques conventions d'écritures,on peut grandement simplifier la syntaxe de l'appel

        ppvNomInterface = ppv

        STNomInterface = Table des vecteurs

        NomInterface = macro d'appel COM,prenant la place de invoke

En changeant toutes les occurences de NomInterface dans la macro ,on forme l'appel a l'interface

NomInterface MACRO Function:REQ, args:VARARG

; definition de la macro locale InvokeInterface

LOCAL InvokeInterface, arg

FOR arg, <args> ;verifier que edx n'est pas dans la liste d'arguments args

IFIDNI <& arg>, <edx> ;

.ERR < edx is not allowed as a coinvoke parameter>

ENDIF

ENDM

IFIDNI <& pInterface>, <edx>

.ERR < edx is not allowed as a coinvoke parameter>

ENDIF

;----------------------------------------------------------------------------------------

;-------------------------- Fin des vérifications -----------------------------------------

;----------------------------------------------------------------------------------------

;InvokeInterface = concatene ...CATSTR(concatene) MACRO instruction MASM32

;---------- on doit mettre ppv en premier argument -----------------------------------

InvokeInterface CATSTR <invoke (STNomInterface PTR[edx]).>,<&Function,ppvNomInterface>

IFNB < args> ; add the list of parameter arguments if any

InvokeInterface CATSTR InvokeInterface, <, >, <&args>

ENDIF

; forme les lignes de codes

mov edx, ppvNomInterface

mov edx, [edx]

InvokeInterface

ENDM

Example:

dans le code,syntaxe de l'appel:

IOleWindow GetWindow,pFenetre ;La macro prend la place de invoke qu'elle utilise

Ne pas oublier de remplirppvIOleWindow ,avant d'utiliser l'interface

dans les déclarations

STIOleWindow STRUCT

;----- Iunknown commune a toutes les interfaces

QueryInterface comethod3 ?

AddRef comethod1 ?

Release comethod1 ?

; IOleWindow methods

GetWindow comethod2 ?

ContextSensitiveHelp comethod2 ?

STIOleWindow ENDS

IOleWindow MACRO Function:REQ, args:VARARG

; definition de la macro locale InvokeInterface

LOCAL InvokeInterface, arg

FOR arg, <args> ;verifier que edx n'est pas dans la liste d'arguments args

IFIDNI <& arg>, <edx> ;

.ERR < edx is not allowed as a coinvoke parameter>

ENDIF

ENDM

IFIDNI <& pInterface>, <edx>

.ERR < edx is not allowed as a coinvoke parameter>

ENDIF

;InvokeInterface = concatene ...CATSTR(concatene) MACRO instruction MASM32

;---------- on doit mettre ppv en premier argument -----------------------------------

InvokeInterface CATSTR <invoke (STIOleWindow PTR[edx]).>,<&Function,ppvIOleWindow>

IFNB < args> ; add the list of parameter arguments if any

InvokeInterface CATSTR InvokeInterface, <, >, <&args>

ENDIF

; forme les lignes de codes

mov edx, ppvIOleWindow

mov edx, [edx]

InvokeInterface

ENDM

Se connecter a une interface par le nom d'un programme:

Dans la base de registre on touve:

[HKEY_CLASSES_ROOT\DExplore.AppObj] celle ci possède une clé Curver=DExplore.AppObj.9.0

[HKEY_CLASSES_ROOT\DExplore.AppObj.9.0] donne un CLSID { ....}

on cherche le CLSID { ....}

On le trouve a ValeurClé=[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{.....}]

\DefaultIcon] @="\"C:\\Program Files\\Fichiers communs\\Microsoft Shared\\Help 9\\dexplore.exe\", 1200"

Et voila,on a le nom et le chemin du programme associé a DExplore.AppObj.9.0

Maintenant pour avoir le reste des informations,on se sert de OLEVIEW (fourni avec le SDK) ou de internet

Pour DExplore.AppObj,oleview trouve une Typelib (header file joint au programme).

      ceci nous donne un clsid pour l'interface Help

Pour DExplore.AppObj.9.0,oleview trouve une Typelib (header file joint au programme).

      ceci nous donne un clsid pour l'interface Help2

Le côté pratique de son emploi:

Plusieurs Méthodes de connection

Garder un oeil sur les valeurs de CLSCTX_ ,responsable de beaucoup d'échec.La fonction CoCreateInstance est capable de choisir la meilleurre parmi les trois qu'elle utilise.

Par ProgId (voir ce mot dans la base de registre)

Pour avoir le pointeur d'interface,le proc retourne la valeur à mettre dans ppv:

invoke GetInterfaceFromProgId,SADR("DExplore.AppObj.9.0"),addr IID_Help2

GetInterfaceFromProgId PROC pProgId:DWORD,pIID:DWORD

LOCAL wszProgID[1024]:word

LOCAL ppv:Dword,retour,pchaine

LOCAL clsid :GUID

mov retour,0

invoke MultiByteToWideChar,CP_ACP,MB_PRECOMPOSED,pProgId,-1,addr wszProgID,sizeof wszProgID

invoke CLSIDFromProgID,addr wszProgID,addr clsid

.if eax == S_OK

;CLSCTX_INPROC_SERVER CLSCTX_LOCAL_SERVER

invoke CoCreateInstance,addr clsid, NULL,CLSCTX_LOCAL_SERVER,pIID,addr ppv

.if eax == S_OK

PuPo retour,ppv

.elseif eax == REGDB_E_CLASSNOTREG ;for debugging use

mov retour,2

.elseif eax == E_OUTOFMEMORY

mov retour,3

.elseif eax == E_INVALIDARG

mov retour,3

.elseif eax == E_UNEXPECTED

mov retour,4

.elseif eax == CLASS_E_NOAGGREGATION

mov retour,5

.else

mov retour,6

.endif

.endif

mov eax,retour

RET

GetInterfaceFromProgId endp

Avec CoCreateInstance

invoke CoCreateInstance,addr CLSID_ShellLink,NULL,

CLSCTX_INPROC_HANDLER OR CLSCTX_INPROC_SERVER OR CLSCTX_LOCAL_SERVER ,addr IID_IUnknown,addr ppvIdispatch

IUnknown QueryInterface,addr IID_IShellLink,addr ppvIShellLink

ou directement

invoke CoCreateInstance,addr CLSID_ShellLink,NULL,

CLSCTX_INPROC_HANDLER OR CLSCTX_INPROC_SERVER OR CLSCTX_LOCAL_SERVER ,addr IID_IShellLink,addr ppvIShellLink

avec une API

CoGetMalloc retourne un pointeur sur Imalloc

Par d'autres interfaces

Voir SDK

Retrouver un IId un CLSID

Le sous répertoire d'editmasm ref\feuilles contient plusieurs fichiers textes en .UUI

Ce sont les valeurs GUID des interfaces extraites des 8 librairies du SDK dont le nom contient UUI
Chercher le nom de l'interface ,exemple, IShellLink et vous trouverez un CLSID_ et un IID_

Dans le source:

includelib uuid.lib

EXTERNDEF CLSID_IShellLink:GUID ;on peut maintenant l'utiliser dans le code

Typelib

Les bibliothèques de types ( typelib) sont des fichiers de déclarations,des headers, utilisées par la dll.Elles sont écrites dans une syntaxe spéciale,l' ODL (Object Description Langage) et enregistré dans un format spécial .tlb créer par midl.Le fichier produit est intégrable au source compilé.

La coclass definit un CLSID les autres uui des IID voir cocreateinstance

ATL

atl.dll facilite l'utilisation des fenêtres comme des containers COM et bien d'autres choses encore


Présentation - DirectDraw - http://www

Vu dans Tutorie DirectDraw - http://www.pageasm.com DRAW1.doc

Présentation - DirectDraw - http://www.pageasm.com

DirectDrawhttp://www.pageasm.com

NavigationPrésentationAccueil

Documents Programmes Tutoriels Windows Initiation Intro. rapide

Les Dialogues Direct Draw Présentation Affichage simple Multiple

Buffer ingImages GIFBlit

Animation (1)

Animation (2)

Le bon choix Tutoriels DOS Index du Site Histoire du Site Liens

L'API de DirectX n'a pas un jeu fixe de fonction comme Win32, tout

simplement parcequ'elle évolue constament. Il existe seulement une fonction

initiale qui permet d'obtenir un pointeur vers un ensemble de fonctions.

Par exemple pour DirectDraw c'est DirectDrawCreateEx:

invoke DirectDrawCreateEx,NULL,addr pDD,addr IID_DIRECTDRAW7,NULL

Ce code permet d'obtenir un pointeur pDD vers toutes les fonctions de DirectDraw.(si DirectX 7 ou supérieur est installé)

Malheureusement on ne peux plus utiliser invoke pour appeler ces fonctions, il faut les appeler soi-même, c'est à dire empiler

tous les arguments du dernier au premier,puis empiler la valeur du pointeur et enfin appeler via celui-ci.

Un exemple:push 0

push 0

push 16

push 480

push 640

mov eax, pDD

push eax

mov eax, [eax]

call dword ptr [eax+DD7_SETDISPLAYMODE]

Ici on appelle SetDisplayMode via le pointeur pDD qu'on a obtenu avec DirectDrawCreateEx.

Etant donné que ceci est très lourd, j'ai créé des macros qui permettent

d'appeler les fonctions un peu comme invoke:

DD7_SetDisplayMode pDD,640,480,16,0,0

Ce code est équivalent au précédent, à la différence que c'est beaucoup plus lisible.

Voici la macro:DD7_SetDisplayMode macro pt, arg1, arg2, arg3, arg4, arg5

mov eax, arg5

push eax

mov eax, arg4

push eax

mov eax, arg3

push eax

mov eax, arg2

push eax

mov eax, arg1

push eax

mov eax, pt

push eax

mov eax, [eax]

call dword ptr [eax+DD7_SETDISPLAYMODE]

endm

Il peut parraître très lourd de tout empiler via eax, mais étant donné

que l'on ne connait pas la nature des arguments, on est obligé de faire

ainsi.Il y a aussi un autre problème, c'est lorsque l'on veut empiler

l'adresse d'une variable.

Avec invoke, on utilise addr, mais ici ce n'est pas possible.

C'est la raison pour laquelle, lorsqu'une adresse sur une variable est

attendue, ceci sera sous-entendu dans la macro. Il faudra donc faire

attention. Un exemple:DD7_CreateSurface macro pt, lparg1, lparg2, arg3

mov eax, arg3

push eax

lea eax, lparg2

push eax

lea eax, lparg1

push eax

mov eax, pt

push eax

mov eax, [eax]

call dword ptr [eax+DD7_CREATESURFACE]

endm

Ceci est incorrect:

DD7_CreateSurface pDD,addr ddsd,addr pDDSPrimary,NULL; incorrect !

Ceci est correct:

DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL; correct

J'insiste donc, faites attention, regardez bien les déclarations

des macros pour savoir si une variable ou une adresse de variable est

attendue (ie xxx = variable; lpxxx = adresse sur variable).

Sachez aussi que je n'ai créé que les macros pour les fonctions utilisées

par ce Tutoriel, donc il faudra soit télécharger la dernière version des

fichiers de déclaration, soit compléter la liste vous-même.


Tutoriel IOS

* Accessing COM Objects by Using C

          Any COM interface method can be called from a C program.
          There are two things to remember when calling an interface method from C:

      -   The first parameter of the method always refers to the object that has been created and that invokes
          the method (the this argument).
      -   Each method in the interface is referenced through a pointer to the object's vtable.

          The following example creates a surface associated with a DirectDraw object by calling
          the IDirectDraw2::CreateSurface method with the C programming language:

             ret = lpDD->lpVtbl->CreateSurface (lpDD, &ddsd, &lpDDS, NULL);

          The lpDD parameter references the DirectDraw object associated with the new surface.
          Incidentally, this method fills a surface-description structure (&ddsd) and returns a pointer to the new surface (&lpDDS).

          To call the IDirectDraw2::CreateSurface method, first dereference the DirectDraw object's vtable,
          and then dereference the method from the vtable. The first parameter supplied in the method is a reference
          to the DirectDraw object that has been created and which invokes the method.

          To illustrate the difference between calling a COM object method in C and C++,
          the same method in C++ is shown below (C++ implicitly dereferences the lpVtbl parameter and passes the this pointer):

             ret = lpDD->CreateSurface(&ddsd, &lpDDS, NULL)

-------------------------------------------------------------------------------------

* Using Macro Definitions   ( Vu dans DXFOUND5_1204Ko.CHM )

          Many of the header files for the DirectX interfaces include macro definitions for each method.
          These macros are included to simplify the use of the methods in your programming.

          The following example uses the IDirectDraw2_CreateSurface macro to call the IDirectDraw2::CreateSurface method.
          The first parameter is a reference to the DirectDraw object that has been created and invokes the method:

          ret = IDirectDraw2_CreateSurface (lpDD, &ddsd, &lpDDS,NULL);

          To obtain a current list of the methods supported by macro definitions, see the appropriate header file for the
          DirectX component you want to use.

  ddraw_LCC ...  #define IDirectDraw2_CreateSurface(p, a, b, c)         (p)->lpVtbl->CreateSurface(p, a, b, c)

      api ... CoCreateInstance

          Creates a single uninitialized object of the class associated with a specified CLSID.
          Call CoCreateInstance when you want to create only one object on the local system.
          To create a single object on a remote system, call CoCreateInstanceEx.
          To create multiple objects based on a single CLSID, refer to the CoGetClassObject function.

      STDAPI  CoCreateInstance(
                                REFCLSID rclsid,    	// Class identifier (CLSID) of the object
                                LPUNKNOWN pUnkOuter,	// Pointer to whether object is or isn’t part of an aggregate
                                DWORD dwClsContext,	 // Context for running executable code
                                REFIID riid,	        // Reference to the identifier of the interface
                                LPVOID * ppv	        // Indirect pointer to requested interface
                               );

  -------------------------------------------------------------------------------- Vu dans _Directx9_c_11486Ko.chm

* Using Macros to Call DirectX COM Methods

  Many of the Microsoft® DirectX® Component Object Model (COM) interfaces have macros defined for each method that simplify using
  the methods in your application. You can find definitions of these macros in the same header file as the interface declaration.
  The macros are designed to be used by both C and C++ applications. To use the C++ macros, you must define _cplusplus.
  Otherwise, the C macros will be used. The macro syntax is the same for both languages, but the header files include separate
  sets of macro definitions that expand to the appropriate calling convention.

  For example, the following code fragment from the d3d.h header file shows the definitions of the C and C++ macros for the
  IDirect3D9::GetAdapterIdentifier method.

  ...
  #define IDirect3D9_GetAdapterIdentifier(p,a,b,c) (p)->lpVtbl->GetAdapterIdentifier(p,a,b,c)
  ...
  #else
  ...
  #define IDirect3D9_GetAdapterIdentifier(p,a,b,c) (p)->GetAdapterIdentifier(a,b,c)
  ...
  #endif

  To use one of these macros, you must first obtain a pointer to the associated interface.
  The first parameter of the macro must be set to that pointer. The remaining parameters map to the method's parameters.

  The macro's return value is the HRESULT value that is returned by the method. The following code fragment uses a macro to call
  the IDirect3D9::GetAdapterIdentifier method. pD3D is a pointer to an IDirect3D9 interface.

       hr = IDirect3D9_GetAdapterIdentifier(pD3D, Adapter, dwFlags, pIdentifier);

  --------------------------------------------------------------------------------

* HRESULT Values

  All COM methods return a 32-bit integer called an HRESULT. With most methods, the HRESULT is essentially a structure that
  contains two primary pieces of information:

  Whether the method succeeded or failed.
  More detailed information about the outcome of the operation supported by the method.
  Some methods return HRESULT values only from the standard set defined in Winerror.h. However, methods are free to return
  custom HRESULT values with more specialized information. These values are normally documented on the method's reference page.

  Note  The list of HRESULT values that you find on a method's reference page is often only a subset of the possible values that
        may be returned. The list typically covers only those values that are specific to the method and those standard values
        that have some method-specific meaning. You should assume that a method may return a variety of standard HRESULT values,
        even if they are not explicitly documented.

  While HRESULT values are often used to return error information, you should not think of them as error codes.
  The fact that the bit that indicates success or failure is stored separately from the bits that contain the
  detailed information allows HRESULT values to have any number of success and failure codes.

  By convention, success codes are given names with an S_ prefix, and failure codes with an E_ prefix.
  For example, the two most commonly used codes are S_OK and E_FAIL, which indicate simple success or failure,respectively.

  The fact that COM methods may return a variety of success or failure codes means that you have to be careful how you test
  the HRESULT value. For example, consider a hypothetical method with documented return values of S_OK if successful
  and E_FAIL if not. However, remember that the method may also return other failure or success codes.
  The following code fragment illustrates the danger of using a simple test.
  The hr value is the HRESULT that was returned by the method.

  if(hr == E_FAIL)
  {
      //Handle the failure
  }

  else
  {
      //Handle the success
  }

  As long as the method returns only E_FAIL to indicate failure, this test will work properly.
  However, the method might also return an error value such as E_NOTIMPL or E_INVALIDARG.
  That value would be interpreted as a success, perhaps causing your application to fail.

  If you need detailed information about the outcome of the method call, you will need to test each relevant HRESULT value.
  However, you may be interested only in whether the method succeeded or failed. A robust way to test whether an HRESULT value
  indicates success or failure is to pass the value to the one of the following macros, defined in Winerror.h.

  The SUCCEEDED macro returns TRUE for a success code and FALSE for a failure code.
  The FAILED    macro returns TRUE for a failure code and FALSE for a success code.
  You can fix the preceding code fragment by using the FAILED macro.

  if(FAILED(hr))
  {
     //Handle the failure
  }

  else
  {
     //Handle the success
  }

  This code fragment properly treats E_NOTIMPL and E_INVALIDARG as failures.

  Although most COM methods return structured HRESULT values, a small number use the HRESULT to return a simple integer.
  Implicitly, these methods are always successful.   If you pass an HRESULT of this sort to the SUCCEEDED macro, the macro will
  always return TRUE. A commonly used example is the IUnknown::Release method. This method decrements an object's reference
  count by one and returns the current reference count. See Managing the Object's Lifetime for a discussion of reference counting.

--------------------------------------------------------------------------

* The Address of a Pointer

  If you view a few COM method reference pages, you will probably run across something like the following:

          HRESULT CreateDevice(..., IDirect3DDevice9 **ppReturnedDeviceInterface);

  While a normal pointer is quite familiar to any C/C++ developer, COM often uses an additional level of indirection.
  This second level of indirection is indicated by a "**" following the type declaration, and the variable name typically
  has a "pp" prefix. For the example given above, ppReturnedDeviceInterface parameter is typically referred to as the address
  of a pointer to an IDirect3DDevice9 interface.

  Unlike C++, you do not access a COM object's methods directly. Instead, you must obtain a pointer to an interface that exposes
  the method. To invoke the method, you use essentially the same syntax that you would to invoke a pointer to a C++ method.
  For example, to invoke the IMyInterface::DoSomething method, you would use the following syntax.

          IMyInterface *pMyIface;
          ...
          pMyIface->DoSomething(...);

  The need for a second level of indirection comes from the fact that you do not create interface pointers directly.
  You must call one of variety of methods, such as the CreateDevice method shown above. To use such a method to obtain an
  interface pointer, you declare a variable as a pointer to the desired interface, and pass the address of that variable
  to the method. In other words, you pass the method the address of a pointer. When the method returns, the variable will
  point to the requested interface, and you can use that pointer to call any of the interface's methods.
  For more information about how to use interface pointers, see Using COM Interfaces.

---------------------------------------------------------------------------------------------------------------------------

* Interface Method Names and Syntax

   All COM interface methods described in this document are shown using C++ class names.
   This naming convention is used for consistency and to differentiate between methods used for different DirectX objects
   that use the same name, such as QueryInterface, AddRef, and Release.
   This does not imply that you can use these methods only with C++.

   In addition, the syntax provided for the methods uses C++ conventions for consistency.
   It does not include the this pointer to the interface.
   When programming in C, the pointer to the interface must be included in each method.
   The following example shows the C++ syntax for the IDirectDraw2::GetCaps method:

          HRESULT GetCaps(
            LPDDCAPS lpDDDriverCaps,
            LPDDCAPS lpDDHELCaps
          );

          The same example using C syntax looks like this:

          HRESULT GetCaps(
            LPDIRECTDRAW lpDD,
            LPDDCAPS lpDDDriverCaps,
            LPDDCAPS lpDDHELCaps
          );

          The lpDD parameter is a pointer to the DirectDraw structure that represents the DirectDraw object.


Tutoriel IOS

 IUnknown Interface ---------------------------------------------

 All COM interfaces are derived from an interface called IUnknown.
 This interface provides DirectX with control of the object's lifetime and the ability to navigate multiple interfaces.
 IUnknown has three methods:

 -   AddRef, which increments the object's reference count by 1 when an interface or another application binds itself to the object.
 -   QueryInterface, which queries the object about the features it supports by requesting pointers to a specific interface.
 -   Release, which decrements the object's reference count by 1. When the count reaches 0, the object is deallocated.

 The AddRef and Release methods maintain an object's reference count. For example, if you create a DirectDrawSurface object,
 the object's reference count is set to 1. Every time a function returns a pointer to an interface for that object, the function
 then must call AddRef through that pointer to increment the reference count. You must match each AddRef call with a call to Release.
 Before the pointer can be destroyed, you must call Release through that pointer.
 After an object's reference count reaches 0, the object is destroyed and all interfaces to it become invalid.

 The QueryInterface method determines whether an object supports a specific interface.
 If an object supports an interface, QueryInterface returns a pointer to that interface.
 You then can use the methods contained in that interface to communicate with the object.
 If QueryInterface successfully returns a pointer to an interface,
 it implicitly calls AddRef to increment the reference count, so your application must call
 Release to decrement the reference count before destroying the pointer to the interface.

 ----------------------------------------------------------------


Tutoriel IOS

          IDirectDraw::GetCaps

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

          DirectDraw Object Types

          You can think of DirectDraw as being composed of several objects that work together.
          This section briefly describes the objects you use when working with the DirectDraw component,
          organized by object type.  For detailed information, see DirectDraw Essentials.

          The DirectDraw component uses the following objects:

     *    DirectDraw          object     The DirectDraw object is the heart of all DirectDraw applications.
                                         It's the first object you create, and you use it to make all other related objects.
                                         You create a DirectDraw object by calling the DirectDrawCreate function.
                                         DirectDraw objects expose their functionality through the IDirectDraw and
                                         IDirectDraw2 interfaces. For more information, see The DirectDraw Object.

     *    DirectDrawSurface   object     The DirectDrawSurface object (casually referred to as a "surface") represents
                                         an area in memory that holds data to be displayed on the monitor as images or
                                         moved to other surfaces. You can create a surface by calling the
                                         IDirectDraw2::CreateSurface method of the DirectDraw object with which it will
                                         be associated. DirectDrawSurface objects expose their functionality through the
                                         IDirectDrawSurface, IDirectDrawSurface2, and IDirectDrawSurface3 interfaces.
                                         For more information, see Surfaces.

     *    DirectDrawPalette   object     The DirectDrawPalette object (casually referred to as a "palette") represents a
                                         16- or 256-color indexed palette to be used with a surface.
                                         It contains a series of indexed RGB triplets that describe colors associated with
                                         values within a surface. You do not use palettes with surfaces that use a pixel
                                         format depth greater that 8 bits. You can create a DirectDrawPalette object by
                                         calling the IDirectDraw2::CreatePalette method. DirectDrawPalette objects expose
                                         their functionality through the IDirectDrawPalette interface.
                                         For more information, see Palettes.

     *    DirectDrawClipper   object     The DirectDrawClipper object (casually referred to as a "clipper") helps you
                                         prevent blitting to certain portions of a surface or beyond the bounds of a surface.
                                         You can create a clipper by calling the IDirectDraw2::CreateClipper method.
                                         DirectDrawClipper objects expose their functionality through the IDirectDrawClipper interface. For more information, see Clippers.

     *    DirectDrawVideoPort object     The DirectDrawVideoPort object represents video-port hardware present in some systems.
                                         This hardware allows direct access to the frame buffer without accessing the CPU or
                                         using the PCI bus. You can create a DirectDrawVideoPort object by calling a
                                         QueryInterface method for the DirectDraw object, specifying the IID_IDDVideoPortContainer
                                         reference identifier. DirectDrawVideoPort objects expose their functionality through the
                                         IDDVideoPortContainer and IDirectDrawVideoPort interfaces.
                                         For more information, see Video Ports.

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

 Accessing COM Objects by Using C

 Any COM interface method can be called from a C program.
 There are two things to remember when calling an interface method from C:

- The first parameter of the method always refers to the object that has been created and that invokes the method (the this argument).
- Each method in the interface is referenced through a pointer to the object's vtable.

 The following example creates a surface associated with a DirectDraw object by calling the IDirectDraw2::CreateSurface method with
 the C programming language:

          ret = lpDD->lpVtbl->CreateSurface (lpDD, &ddsd, &lpDDS,  NULL);

 The lpDD parameter references the DirectDraw object associated with the new surface. Incidentally, this method fills a
 surface-description structure (&ddsd) and returns a pointer to the new surface (&lpDDS).

 To call the IDirectDraw2::CreateSurface method, first dereference the DirectDraw object's vtable, and then dereference
 the method from the vtable. The first parameter supplied in the method is a reference to the DirectDraw object that has
 been created and which invokes the method.

 To illustrate the difference between calling a COM object method in C and C++, the same method in C++ is shown below
 (C++ implicitly dereferences the lpVtbl parameter and passes the this pointer):

          ret = lpDD->CreateSurface(&ddsd, &lpDDS, NULL)

-------------------------------------------------------------------

Macros    This section describes the following macros used in DirectInput:

DIDFT_GETINSTANCE DIDFT_GETTYPE DIEFT_GETTYPE DISEQUENCE_COMPARE GET_DIDEVICE_SUBTYPE GET_DIDEVICE_TYPE DIMAKEUSAGEDWORD

          Dinput.h also defines macros for C calls to all the methods of the IDirectInput and IDirectInputDevice interfaces.
          These macros eliminate the need for pointers to method tables.
          For example, here is a C call to the IDirectInputDevice::Release method:

          lpdid->lpVtbl->Release(lpdid));

          The equivalent macro call looks like this:

          IDirectInputDevice_Release(lpdid);

          All these macros take the same parameters as the method calls themselves.

     STDMETHOD_(ULONG,Release)     (THIS) PURE;

  #define IDirectDraw_Release(p)   (p)->lpVtbl->Release(p)      ; #if !defined(__cplusplus) .. soit en  C
  #define IDirectDraw_Release(p)   (p)->Release()               ; else                      .. soit en  C++

     STDMETHOD(CreateSurface)      (THIS_  LPDDSURFACEDESC, LPDIRECTDRAWSURFACE FAR *, IUnknown FAR *) PURE;

  #define IDirectDraw_CreateSurface(p, a, b, c)       (p)->lpVtbl->CreateSurface(p, a, b, c)
  #define IDirectDraw*_CreateSurface(p, a, b, c)       (p)->CreateSurface(a, b, c)

    ret = IDirectDraw_CreateSurface (lpDD, &ddsd, &lpDDS,NULL);

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

          Release         macro pt
                  mov     eax,  pt
                  push    eax
                  mov     edi, [eax]
                  call    dword ptr [edi+8]
                  mov     pt, 0
                          endm

              eax=addr00                         edi=addr10
       (p)       ->         lpVtb1                  ->                        Release

        pt                [eax+0]  [eax+4]  [eax+8]         [edi+0]  [edi+4]  [edi+8]  [edi+12]
        |                 |                                 |        |        |        |
   -//--+- addr00 -+--//--+-addr10-+-addr20-+-addr30-+--//--+--Ad10--+--Ad11--+--Ad12--+--Ad13--+--//--+--Ad20--+--Ad21--+--Ad22--//--+--Ad30--+--//--+--Ad40--+--Ad41--+--Ad42--//
                          |                                 |                                          |                              |               |
                          addr00                            addr10                                     addr20                         addr30          addr40

       pt       DWORD  addr00

       addr00   DWORD  addr10
                DWORD  addr20
                DWORD  addr30
                DWORD  addr40
                 ---
       addr10   DWORD  Ad10        QueryInterface
                DWORD  Ad11        AddRef
                DWORD  Ad12        release
                 ---
       addr20   DWORD  Ad20
                DWORD  Ad21
                DWORD  Ad22
                 ---
       addr30   DWORD  Ad30
                DWORD  Ad31
                DWORD  Ad32
                 ---
       addr40   DWORD  Ad40
                DWORD  Ad41
                DWORD  Ad42
                 ---

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR
-

          /*
           * INTERACES FOLLOW:
           *	IDirectDraw
           *	IDirectDrawClipper
           *	IDirectDrawPalette
           *	IDirectDrawSurface
           */

          DEFINE_GUID( CLSID_DirectDraw,			               0xD7B70EE0,0x4340,0x11CF,0xB0,0x63,0x00,0x20,0xAF,0xC2,0xCD,0x35 );
          DEFINE_GUID( CLSID_DirectDrawClipper,           0x593817A0,0x7DB3,0x11CF,0xA2,0xDE,0x00,0xAA,0x00,0xb9,0x33,0x56 );
          DEFINE_GUID( IID_IDirectDraw,		                	0x6C14DB80,0xA733,0x11CE,0xA5,0x21,0x00,0x20,0xAF,0x0B,0xE5,0x60 );
          DEFINE_GUID( IID_IDirectDraw2,                  0xB3A6F3E0,0x2B43,0x11CF,0xA2,0xDE,0x00,0xAA,0x00,0xB9,0x33,0x56 );
          DEFINE_GUID( IID_IDirectDrawSurface,          		0x6C14DB81,0xA733,0x11CE,0xA5,0x21,0x00,0x20,0xAF,0x0B,0xE5,0x60 );
          DEFINE_GUID( IID_IDirectDrawSurface2,	     	    0x57805885,0x6eec,0x11cf,0x94,0x41,0xa8,0x23,0x03,0xc1,0x0e,0x27 );
          DEFINE_GUID( IID_IDirectDrawSurface3,           0xDA044E00,0x69B2,0x11D0,0xA1,0xD5,0x00,0xAA,0x00,0xB8,0xDF,0xBB );
          DEFINE_GUID( IID_IDirectDrawPalette,          		0x6C14DB84,0xA733,0x11CE,0xA5,0x21,0x00,0x20,0xAF,0x0B,0xE5,0x60 );
          DEFINE_GUID( IID_IDirectDrawClipper,		          0x6C14DB85,0xA733,0x11CE,0xA5,0x21,0x00,0x20,0xAF,0x0B,0xE5,0x60 );
          DEFINE_GUID( IID_IDirectDrawColorControl,      	0x4B9F0EE0,0x0D7E,0x11D0,0x9B,0x06,0x00,0xA0,0xC9,0x03,0xA3,0xB8 );

          struct IDirectDraw;
          struct IDirectDrawSurface;
          struct IDirectDrawPalette;
          struct IDirectDrawClipper;

          typedef struct IDirectDraw		            	FAR *LPDIRECTDRAW;
          typedef struct IDirectDraw2		           	FAR *LPDIRECTDRAW2;
          typedef struct IDirectDrawSurface		      FAR *LPDIRECTDRAWSURFACE;
          typedef struct IDirectDrawSurface2     		FAR *LPDIRECTDRAWSURFACE2;
          typedef struct IDirectDrawSurface3    	 	FAR *LPDIRECTDRAWSURFACE3;

          typedef struct IDirectDrawPalette		      FAR *LPDIRECTDRAWPALETTE;
          typedef struct IDirectDrawClipper		      FAR *LPDIRECTDRAWCLIPPER;
          typedef struct IDirectDrawColorControl	 	FAR *LPDIRECTDRAWCOLORCONTROL;

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

DECLARE_INTERFACE_( IDirectDraw, IUnknown )        PURE .. VIERGE .. NATUREL .. PURE
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)        (THIS_ REFIID riid, LPVOID FAR * ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)         (THIS) PURE;
    STDMETHOD_(ULONG,Release)        (THIS) PURE;
    /*** IDirectDraw methods ***/
    STDMETHOD(Compact)               (THIS) PURE;
    STDMETHOD(CreateClipper)         (THIS_ DWORD, LPDIRECTDRAWCLIPPER FAR*, IUnknown FAR * ) PURE;
    STDMETHOD(CreatePalette)         (THIS_ DWORD, LPPALETTEENTRY, LPDIRECTDRAWPALETTE FAR*, IUnknown FAR * ) PURE;
    STDMETHOD(CreateSurface)         (THIS_  LPDDSURFACEDESC, LPDIRECTDRAWSURFACE FAR *, IUnknown FAR *) PURE;
    STDMETHOD(DuplicateSurface)      (THIS_ LPDIRECTDRAWSURFACE, LPDIRECTDRAWSURFACE FAR * ) PURE;
    STDMETHOD(EnumDisplayModes)      (THIS_ DWORD, LPDDSURFACEDESC, LPVOID, LPDDENUMMODESCALLBACK ) PURE;
    STDMETHOD(EnumSurfaces)          (THIS_ DWORD, LPDDSURFACEDESC, LPVOID,LPDDENUMSURFACESCALLBACK ) PURE;
    STDMETHOD(FlipToGDISurface)      (THIS) PURE;
    STDMETHOD(GetCaps)               (THIS_ LPDDCAPS, LPDDCAPS) PURE;
    STDMETHOD(GetDisplayMode)        (THIS_ LPDDSURFACEDESC) PURE;
    STDMETHOD(GetFourCCCodes)        (THIS_ LPDWORD, LPDWORD ) PURE;
    STDMETHOD(GetGDISurface)         (THIS_ LPDIRECTDRAWSURFACE FAR *) PURE;
    STDMETHOD(GetMonitorFrequency)   (THIS_ LPDWORD) PURE;
    STDMETHOD(GetScanLine)           (THIS_ LPDWORD) PURE;
    STDMETHOD(GetVerticalBlankStatus)(THIS_ LPBOOL ) PURE;
    STDMETHOD(Initialize)            (THIS_ GUID FAR *) PURE;
    STDMETHOD(RestoreDisplayMode)    (THIS) PURE;
    STDMETHOD(SetCooperativeLevel)   (THIS_ HWND, DWORD) PURE;
    STDMETHOD(SetDisplayMode)        (THIS_ DWORD, DWORD,DWORD) PURE;
    STDMETHOD(WaitForVerticalBlank)  (THIS_ DWORD, HANDLE ) PURE;
};

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

.data?
 pDD                 LPDIRECTDRAW7    ?
 pDDSPrimary  LPDIRECTDRAWSURFACE7    ?
 ddsd               DDSURFACEDESC2   <>
.code

          IDirectDraw_CreateSurface macro to call the IDirectDraw::CreateSurface method.

          ret = IDirectDraw_CreateSurface (lpDD, &ddsd, &lpDDS,NULL);

          avec  invoke  DirectDrawCreateEx,NULL,addr lpDD,addr IID_IDIRECTDRAW7,NULL

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

#if !defined(__cplusplus) || defined(CINTERFACE)
#define IDirectDraw_QueryInterface(p, a, b)         (p)->lpVtbl->QueryInterface(p, a, b)
#define IDirectDraw_AddRef(p)                       (p)->lpVtbl->AddRef(p)
#define IDirectDraw_Release(p)                      (p)->lpVtbl->Release(p)
#define IDirectDraw_Compact(p)                      (p)->lpVtbl->Compact(p)
#define IDirectDraw_CreateClipper(p, a, b, c)       (p)->lpVtbl->CreateClipper(p, a, b, c)
#define IDirectDraw_CreatePalette(p, a, b, c, d)    (p)->lpVtbl->CreatePalette(p, a, b, c, d)
#define IDirectDraw_CreateSurface(p, a, b, c)       (p)->lpVtbl->CreateSurface(p, a, b, c)
#define IDirectDraw_DuplicateSurface(p, a, b)       (p)->lpVtbl->DuplicateSurface(p, a, b)
#define IDirectDraw_EnumDisplayModes(p, a, b, c, d) (p)->lpVtbl->EnumDisplayModes(p, a, b, c, d)
#define IDirectDraw_EnumSurfaces(p, a, b, c, d)     (p)->lpVtbl->EnumSurfaces(p, a, b, c, d)
#define IDirectDraw_FlipToGDISurface(p)             (p)->lpVtbl->FlipToGDISurface(p)
#define IDirectDraw_GetCaps(p, a, b)                (p)->lpVtbl->GetCaps(p, a, b)
#define IDirectDraw_GetDisplayMode(p, a)            (p)->lpVtbl->GetDisplayMode(p, a)
#define IDirectDraw_GetFourCCCodes(p, a, b)         (p)->lpVtbl->GetFourCCCodes(p, a, b)
#define IDirectDraw_GetGDISurface(p, a)             (p)->lpVtbl->GetGDISurface(p, a)
#define IDirectDraw_GetMonitorFrequency(p, a)       (p)->lpVtbl->GetMonitorFrequency(p, a)
#define IDirectDraw_GetScanLine(p, a)               (p)->lpVtbl->GetScanLine(p, a)
#define IDirectDraw_GetVerticalBlankStatus(p, a)    (p)->lpVtbl->GetVerticalBlankStatus(p, a)
#define IDirectDraw_Initialize(p, a)                (p)->lpVtbl->Initialize(p, a)
#define IDirectDraw_RestoreDisplayMode(p)           (p)->lpVtbl->RestoreDisplayMode(p)
#define IDirectDraw_SetCooperativeLevel(p, a, b)    (p)->lpVtbl->SetCooperativeLevel(p, a, b)
#define IDirectDraw_SetDisplayMode(p, a, b, c)      (p)->lpVtbl->SetDisplayMode(p, a, b, c)
#define IDirectDraw_WaitForVerticalBlank(p, a, b)   (p)->lpVtbl->WaitForVerticalBlank(p, a, b)
#else
#define IDirectDraw_QueryInterface(p, a, b)         (p)->QueryInterface(a, b)
#define IDirectDraw_AddRef(p)                       (p)->AddRef()
#define IDirectDraw_Release(p)                      (p)->Release()
#define IDirectDraw_Compact(p)                      (p)->Compact()
#define IDirectDraw_CreateClipper(p, a, b, c)       (p)->CreateClipper(a, b, c)
#define IDirectDraw_CreatePalette(p, a, b, c, d)    (p)->CreatePalette(a, b, c, d)
#define IDirectDraw_CreateSurface(p, a, b, c)       (p)->CreateSurface(a, b, c)
#define IDirectDraw_DuplicateSurface(p, a, b)       (p)->DuplicateSurface(a, b)
#define IDirectDraw_EnumDisplayModes(p, a, b, c, d) (p)->EnumDisplayModes(a, b, c, d)
#define IDirectDraw_EnumSurfaces(p, a, b, c, d)     (p)->EnumSurfaces(a, b, c, d)
#define IDirectDraw_FlipToGDISurface(p)             (p)->FlipToGDISurface()
#define IDirectDraw_GetCaps(p, a, b)                (p)->GetCaps(a, b)
#define IDirectDraw_GetDisplayMode(p, a)            (p)->GetDisplayMode(a)
#define IDirectDraw_GetFourCCCodes(p, a, b)         (p)->GetFourCCCodes(a, b)
#define IDirectDraw_GetGDISurface(p, a)             (p)->GetGDISurface(a)
#define IDirectDraw_GetMonitorFrequency(p, a)       (p)->GetMonitorFrequency(a)
#define IDirectDraw_GetScanLine(p, a)               (p)->GetScanLine(a)
#define IDirectDraw_GetVerticalBlankStatus(p, a)    (p)->GetVerticalBlankStatus(a)
#define IDirectDraw_Initialize(p, a)                (p)->Initialize(a)
#define IDirectDraw_RestoreDisplayMode(p)           (p)->RestoreDisplayMode()
#define IDirectDraw_SetCooperativeLevel(p, a, b)    (p)->SetCooperativeLevel(a, b)
#define IDirectDraw_SetDisplayMode(p, a, b, c)      (p)->SetDisplayMode(a, b, c)
#define IDirectDraw_WaitForVerticalBlank(p, a, b)   (p)->WaitForVerticalBlank(a, b)
#endif

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

DECLARE_INTERFACE_( IDirectDraw2, IUnknown )
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)        (THIS_ REFIID riid, LPVOID FAR * ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)         (THIS)  PURE;
    STDMETHOD_(ULONG,Release)        (THIS) PURE;
    /*** IDirectDraw methods ***/
    STDMETHOD(Compact)               (THIS) PURE;
    STDMETHOD(CreateClipper)         (THIS_ DWORD, LPDIRECTDRAWCLIPPER FAR*, IUnknown FAR * ) PURE;
    STDMETHOD(CreatePalette)         (THIS_ DWORD, LPPALETTEENTRY, LPDIRECTDRAWPALETTE FAR*, IUnknown FAR * ) PURE;
    STDMETHOD(CreateSurface)         (THIS_  LPDDSURFACEDESC, LPDIRECTDRAWSURFACE FAR *, IUnknown FAR *) PURE;
    STDMETHOD(DuplicateSurface)      (THIS_ LPDIRECTDRAWSURFACE, LPDIRECTDRAWSURFACE FAR * ) PURE;
    STDMETHOD(EnumDisplayModes)      (THIS_ DWORD, LPDDSURFACEDESC, LPVOID, LPDDENUMMODESCALLBACK ) PURE;
    STDMETHOD(EnumSurfaces)          (THIS_ DWORD, LPDDSURFACEDESC, LPVOID,LPDDENUMSURFACESCALLBACK ) PURE;
    STDMETHOD(FlipToGDISurface)      (THIS) PURE;
    STDMETHOD(GetCaps)               (THIS_ LPDDCAPS, LPDDCAPS) PURE;
    STDMETHOD(GetDisplayMode)        (THIS_ LPDDSURFACEDESC) PURE;
    STDMETHOD(GetFourCCCodes)        (THIS_  LPDWORD, LPDWORD ) PURE;
    STDMETHOD(GetGDISurface)         (THIS_ LPDIRECTDRAWSURFACE FAR *) PURE;
    STDMETHOD(GetMonitorFrequency)   (THIS_ LPDWORD) PURE;
    STDMETHOD(GetScanLine)           (THIS_ LPDWORD) PURE;
    STDMETHOD(GetVerticalBlankStatus)(THIS_ LPBOOL ) PURE;
    STDMETHOD(Initialize)            (THIS_ GUID FAR *) PURE;
    STDMETHOD(RestoreDisplayMode)    (THIS) PURE;
    STDMETHOD(SetCooperativeLevel)   (THIS_ HWND, DWORD) PURE;
    STDMETHOD(SetDisplayMode)        (THIS_ DWORD, DWORD,DWORD, DWORD, DWORD) PURE;
    STDMETHOD(WaitForVerticalBlank)  (THIS_ DWORD, HANDLE ) PURE;
    /*** Added in the v2 interface ***/
    STDMETHOD(GetAvailableVidMem)    (THIS_ LPDDSCAPS, LPDWORD, LPDWORD) PURE;
};

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

DECLARE_INTERFACE_( IDirectDrawPalette, IUnknown )
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)        (THIS_ REFIID riid, LPVOID FAR * ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)         (THIS)  PURE;
    STDMETHOD_(ULONG,Release)        (THIS) PURE;
    /*** IDirectDrawPalette methods ***/
    STDMETHOD(GetCaps)               (THIS_ LPDWORD) PURE;
    STDMETHOD(GetEntries)            (THIS_ DWORD,DWORD,DWORD,LPPALETTEENTRY) PURE;
    STDMETHOD(Initialize)            (THIS_ LPDIRECTDRAW, DWORD, LPPALETTEENTRY) PURE;
    STDMETHOD(SetEntries)            (THIS_ DWORD,DWORD,DWORD,LPPALETTEENTRY) PURE;
};

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

DECLARE_INTERFACE_( IDirectDrawClipper, IUnknown )
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)        (THIS_ REFIID riid, LPVOID FAR * ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)         (THIS)  PURE;
    STDMETHOD_(ULONG,Release)        (THIS) PURE;
    /*** IDirectDrawClipper methods ***/
    STDMETHOD(GetClipList)           (THIS_ LPRECT, LPRGNDATA, LPDWORD) PURE;
    STDMETHOD(GetHWnd)               (THIS_ HWND FAR *) PURE;
    STDMETHOD(Initialize)            (THIS_ LPDIRECTDRAW, DWORD) PURE;
    STDMETHOD(IsClipListChanged)     (THIS_ BOOL FAR *) PURE;
    STDMETHOD(SetClipList)           (THIS_ LPRGNDATA,DWORD) PURE;
    STDMETHOD(SetHWnd)               (THIS_ DWORD, HWND ) PURE;
};

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

DECLARE_INTERFACE_( IDirectDrawSurface, IUnknown )
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)        (THIS_ REFIID riid, LPVOID FAR * ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)         (THIS)  PURE;
    STDMETHOD_(ULONG,Release)        (THIS) PURE;
    /*** IDirectDrawSurface methods ***/
    STDMETHOD(AddAttachedSurface)    (THIS_ LPDIRECTDRAWSURFACE) PURE;
    STDMETHOD(AddOverlayDirtyRect)   (THIS_ LPRECT) PURE;
    STDMETHOD(Blt)                   (THIS_ LPRECT,LPDIRECTDRAWSURFACE, LPRECT,DWORD, LPDDBLTFX) PURE;
    STDMETHOD(BltBatch)              (THIS_ LPDDBLTBATCH, DWORD, DWORD ) PURE;
    STDMETHOD(BltFast)               (THIS_ DWORD,DWORD,LPDIRECTDRAWSURFACE, LPRECT,DWORD) PURE;
    STDMETHOD(DeleteAttachedSurface) (THIS_ DWORD,LPDIRECTDRAWSURFACE) PURE;
    STDMETHOD(EnumAttachedSurfaces)  (THIS_ LPVOID,LPDDENUMSURFACESCALLBACK) PURE;
    STDMETHOD(EnumOverlayZOrders)    (THIS_ DWORD,LPVOID,LPDDENUMSURFACESCALLBACK) PURE;
    STDMETHOD(Flip)                  (THIS_ LPDIRECTDRAWSURFACE, DWORD) PURE;
    STDMETHOD(GetAttachedSurface)    (THIS_ LPDDSCAPS, LPDIRECTDRAWSURFACE FAR *) PURE;
    STDMETHOD(GetBltStatus)          (THIS_ DWORD) PURE;
    STDMETHOD(GetCaps)               (THIS_ LPDDSCAPS) PURE;
    STDMETHOD(GetClipper)            (THIS_ LPDIRECTDRAWCLIPPER FAR*) PURE;
    STDMETHOD(GetColorKey)           (THIS_ DWORD, LPDDCOLORKEY) PURE;
    STDMETHOD(GetDC)                 (THIS_ HDC FAR *) PURE;
    STDMETHOD(GetFlipStatus)         (THIS_ DWORD) PURE;
    STDMETHOD(GetOverlayPosition)    (THIS_ LPLONG, LPLONG ) PURE;
    STDMETHOD(GetPalette)            (THIS_ LPDIRECTDRAWPALETTE FAR*) PURE;
    STDMETHOD(GetPixelFormat)        (THIS_ LPDDPIXELFORMAT) PURE;
    STDMETHOD(GetSurfaceDesc)        (THIS_ LPDDSURFACEDESC) PURE;
    STDMETHOD(Initialize)            (THIS_ LPDIRECTDRAW, LPDDSURFACEDESC) PURE;
    STDMETHOD(IsLost)                (THIS) PURE;
    STDMETHOD(Lock)                  (THIS_ LPRECT,LPDDSURFACEDESC,DWORD,HANDLE) PURE;
    STDMETHOD(ReleaseDC)             (THIS_ HDC) PURE;
    STDMETHOD(Restore)               (THIS) PURE;
    STDMETHOD(SetClipper)            (THIS_ LPDIRECTDRAWCLIPPER) PURE;
    STDMETHOD(SetColorKey)           (THIS_ DWORD, LPDDCOLORKEY) PURE;
    STDMETHOD(SetOverlayPosition)    (THIS_ LONG, LONG ) PURE;
    STDMETHOD(SetPalette)            (THIS_ LPDIRECTDRAWPALETTE) PURE;
    STDMETHOD(Unlock)                (THIS_ LPVOID) PURE;
    STDMETHOD(UpdateOverlay)         (THIS_ LPRECT, LPDIRECTDRAWSURFACE,LPRECT,DWORD, LPDDOVERLAYFX) PURE;
    STDMETHOD(UpdateOverlayDisplay)  (THIS_ DWORD) PURE;
    STDMETHOD(UpdateOverlayZOrder)   (THIS_ DWORD, LPDIRECTDRAWSURFACE) PURE;
};

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

DECLARE_INTERFACE_( IDirectDrawSurface2, IUnknown )
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)        (THIS_ REFIID riid, LPVOID FAR * ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)         (THIS)  PURE;
    STDMETHOD_(ULONG,Release)        (THIS) PURE;
    /*** IDirectDrawSurface methods ***/
    STDMETHOD(AddAttachedSurface)    (THIS_ LPDIRECTDRAWSURFACE2) PURE;
    STDMETHOD(AddOverlayDirtyRect)   (THIS_ LPRECT) PURE;
    STDMETHOD(Blt)                   (THIS_ LPRECT,LPDIRECTDRAWSURFACE2, LPRECT,DWORD, LPDDBLTFX) PURE;
    STDMETHOD(BltBatch)              (THIS_ LPDDBLTBATCH, DWORD, DWORD ) PURE;
    STDMETHOD(BltFast)               (THIS_ DWORD,DWORD,LPDIRECTDRAWSURFACE2, LPRECT,DWORD) PURE;
    STDMETHOD(DeleteAttachedSurface) (THIS_ DWORD,LPDIRECTDRAWSURFACE2) PURE;
    STDMETHOD(EnumAttachedSurfaces)  (THIS_ LPVOID,LPDDENUMSURFACESCALLBACK) PURE;
    STDMETHOD(EnumOverlayZOrders)    (THIS_ DWORD,LPVOID,LPDDENUMSURFACESCALLBACK) PURE;
    STDMETHOD(Flip)                  (THIS_ LPDIRECTDRAWSURFACE2, DWORD) PURE;
    STDMETHOD(GetAttachedSurface)    (THIS_ LPDDSCAPS, LPDIRECTDRAWSURFACE2 FAR *) PURE;
    STDMETHOD(GetBltStatus)          (THIS_ DWORD) PURE;
    STDMETHOD(GetCaps)               (THIS_ LPDDSCAPS) PURE;
    STDMETHOD(GetClipper)            (THIS_ LPDIRECTDRAWCLIPPER FAR*) PURE;
    STDMETHOD(GetColorKey)           (THIS_ DWORD, LPDDCOLORKEY) PURE;
    STDMETHOD(GetDC)                 (THIS_ HDC FAR *) PURE;
    STDMETHOD(GetFlipStatus)         (THIS_ DWORD) PURE;
    STDMETHOD(GetOverlayPosition)    (THIS_ LPLONG, LPLONG ) PURE;
    STDMETHOD(GetPalette)            (THIS_ LPDIRECTDRAWPALETTE FAR*) PURE;
    STDMETHOD(GetPixelFormat)        (THIS_ LPDDPIXELFORMAT) PURE;
    STDMETHOD(GetSurfaceDesc)        (THIS_ LPDDSURFACEDESC) PURE;
    STDMETHOD(Initialize)            (THIS_ LPDIRECTDRAW, LPDDSURFACEDESC) PURE;
    STDMETHOD(IsLost)                (THIS) PURE;
    STDMETHOD(Lock)                  (THIS_ LPRECT,LPDDSURFACEDESC,DWORD,HANDLE) PURE;
    STDMETHOD(ReleaseDC)             (THIS_ HDC) PURE;
    STDMETHOD(Restore)               (THIS) PURE;
    STDMETHOD(SetClipper)            (THIS_ LPDIRECTDRAWCLIPPER) PURE;
    STDMETHOD(SetColorKey)           (THIS_ DWORD, LPDDCOLORKEY) PURE;
    STDMETHOD(SetOverlayPosition)    (THIS_ LONG, LONG ) PURE;
    STDMETHOD(SetPalette)            (THIS_ LPDIRECTDRAWPALETTE) PURE;
    STDMETHOD(Unlock)                (THIS_ LPVOID) PURE;
    STDMETHOD(UpdateOverlay)         (THIS_ LPRECT, LPDIRECTDRAWSURFACE2,LPRECT,DWORD, LPDDOVERLAYFX) PURE;
    STDMETHOD(UpdateOverlayDisplay)  (THIS_ DWORD) PURE;
    STDMETHOD(UpdateOverlayZOrder)   (THIS_ DWORD, LPDIRECTDRAWSURFACE2) PURE;
    /*** Added in the v2 interface ***/
    STDMETHOD(GetDDInterface)        (THIS_ LPVOID FAR *) PURE;
    STDMETHOD(PageLock)              (THIS_ DWORD) PURE;
    STDMETHOD(PageUnlock)            (THIS_ DWORD) PURE;
};

PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

DECLARE_INTERFACE_( IDirectDrawSurface3, IUnknown )
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)        (THIS_ REFIID riid, LPVOID FAR * ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)         (THIS)  PURE;
    STDMETHOD_(ULONG,Release)        (THIS) PURE;
    /*** IDirectDrawSurface methods ***/
    STDMETHOD(AddAttachedSurface)    (THIS_ LPDIRECTDRAWSURFACE3) PURE;
    STDMETHOD(AddOverlayDirtyRect)   (THIS_ LPRECT) PURE;
    STDMETHOD(Blt)                   (THIS_ LPRECT,LPDIRECTDRAWSURFACE3, LPRECT,DWORD, LPDDBLTFX) PURE;
    STDMETHOD(BltBatch)              (THIS_ LPDDBLTBATCH, DWORD, DWORD ) PURE;
    STDMETHOD(BltFast)               (THIS_ DWORD,DWORD,LPDIRECTDRAWSURFACE3, LPRECT,DWORD) PURE;
    STDMETHOD(DeleteAttachedSurface) (THIS_ DWORD,LPDIRECTDRAWSURFACE3) PURE;
    STDMETHOD(EnumAttachedSurfaces)  (THIS_ LPVOID,LPDDENUMSURFACESCALLBACK) PURE;
    STDMETHOD(EnumOverlayZOrders)    (THIS_ DWORD,LPVOID,LPDDENUMSURFACESCALLBACK) PURE;
    STDMETHOD(Flip)                  (THIS_ LPDIRECTDRAWSURFACE3, DWORD) PURE;
    STDMETHOD(GetAttachedSurface)    (THIS_ LPDDSCAPS, LPDIRECTDRAWSURFACE3 FAR *) PURE;
    STDMETHOD(GetBltStatus)          (THIS_ DWORD) PURE;
    STDMETHOD(GetCaps)               (THIS_ LPDDSCAPS) PURE;
    STDMETHOD(GetClipper)            (THIS_ LPDIRECTDRAWCLIPPER FAR*) PURE;
    STDMETHOD(GetColorKey)           (THIS_ DWORD, LPDDCOLORKEY) PURE;
    STDMETHOD(GetDC)                 (THIS_ HDC FAR *) PURE;
    STDMETHOD(GetFlipStatus)         (THIS_ DWORD) PURE;
    STDMETHOD(GetOverlayPosition)    (THIS_ LPLONG, LPLONG ) PURE;
    STDMETHOD(GetPalette)            (THIS_ LPDIRECTDRAWPALETTE FAR*) PURE;
    STDMETHOD(GetPixelFormat)        (THIS_ LPDDPIXELFORMAT) PURE;
    STDMETHOD(GetSurfaceDesc)        (THIS_ LPDDSURFACEDESC) PURE;
    STDMETHOD(Initialize)            (THIS_ LPDIRECTDRAW, LPDDSURFACEDESC) PURE;
    STDMETHOD(IsLost)                (THIS) PURE;
    STDMETHOD(Lock)                  (THIS_ LPRECT,LPDDSURFACEDESC,DWORD,HANDLE) PURE;
    STDMETHOD(ReleaseDC)             (THIS_ HDC) PURE;
    STDMETHOD(Restore)               (THIS) PURE;
    STDMETHOD(SetClipper)            (THIS_ LPDIRECTDRAWCLIPPER) PURE;
    STDMETHOD(SetColorKey)           (THIS_ DWORD, LPDDCOLORKEY) PURE;
    STDMETHOD(SetOverlayPosition)    (THIS_ LONG, LONG ) PURE;
    STDMETHOD(SetPalette)            (THIS_ LPDIRECTDRAWPALETTE) PURE;
    STDMETHOD(Unlock)                (THIS_ LPVOID) PURE;
    STDMETHOD(UpdateOverlay)         (THIS_ LPRECT, LPDIRECTDRAWSURFACE3,LPRECT,DWORD, LPDDOVERLAYFX) PURE;
    STDMETHOD(UpdateOverlayDisplay)  (THIS_ DWORD) PURE;
    STDMETHOD(UpdateOverlayZOrder)   (THIS_ DWORD, LPDIRECTDRAWSURFACE3) PURE;
    /*** Added in the v2 interface ***/
    STDMETHOD(GetDDInterface)        (THIS_ LPVOID FAR *) PURE;
    STDMETHOD(PageLock)              (THIS_ DWORD) PURE;
    STDMETHOD(PageUnlock)            (THIS_ DWORD) PURE;
    /*** Added in the V3 interface ***/
    STDMETHOD(SetSurfaceDesc)        (THIS_ LPDDSURFACEDESC, DWORD) PURE;
};

  PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR PTR

DECLARE_INTERFACE_( IDirectDrawColorControl, IUnknown )
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)        (THIS_ REFIID riid, LPVOID FAR * ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)         (THIS)  PURE;
    STDMETHOD_(ULONG,Release)        (THIS) PURE;
    /*** IDirectDrawColorControl methods ***/
    STDMETHOD(GetColorControls)      (THIS_ LPDDCOLORCONTROL) PURE;
    STDMETHOD(SetColorControls)      (THIS_ LPDDCOLORCONTROL) PURE;
};

          Release         macro   pt
                  mov     eax, pt
                  push    eax
                  mov     eax, [eax]
                  call    dword ptr [eax+8]
                  mov     pt, 0
                          endm


CoCreateInstance

Creates a single uninitialized object of the class associated with a specified CLSID. Call CoCreateInstance when you want to create only one object on the local system. To create a single object on a remote system, call CoCreateInstanceEx. To create multiple objects based on a single CLSID, refer to the CoGetClassObject function.

STDAPI CoCreateInstance(

    REFCLSID rclsid,	       // Class identifier (CLSID) of the object
    LPUNKNOWN pUnkOuter,    	// Pointer to whether object is or isn’t part of an aggregate
    DWORD dwClsContext,   	// Context for running executable code
    REFIID riid,	             // Reference to the identifier of the interface
    LPVOID * ppv	             // Indirect pointer to requested interface
   );
Parameters

rclsid

[in] CLSID associated with the data and code that will be used to create the object.

pUnkOuter

[in] If NULL, indicates that the object is not being created as part of an aggregate. If non-NULL, pointer to the aggregate object’s IUnknown interface (the controlling IUnknown).

dwClsContext

[in] Context in which the code that manages the newly created object will run. The values are taken from the enumeration CLSCTX.

riid

[in] Reference to the identifier of the interface to be used to communicate with the object.

ppv

[out] Indirect pointer to the requested interface.

Return Values

S_OK

An instance of the specified object class was successfully created.

REGDB_E_CLASSNOTREG

A specified class is not registered in the registration database. Also can indicate that the type of server you requested in the CLSCTX enumeration is not registered or the values for the server types in the registry are corrupt.

CLASS_E_NOAGGREGATION

This class cannot be created as part of an aggregate.

Remarks

The CoCreateInstance helper function provides a convenient shortcut by connecting to the class object associated with the specified CLSID, creating an uninitialized instance, and releasing the class object. As such, it encapsulates the following functionality:

CoGetClassObject(rclsid, dwClsContext, NULL, IID_IClassFactory, &pCF);
hresult = pCF->CreateInstance(pUnkOuter, riid, ppvObj)
pCF->Release();

It is convenient to use CoCreateInstance when you need to create only a single instance of an object on the local machine. If you are creating an instance on remote machine, call CoCreateInstanceEx. When you are creating multiple instances, it is more efficient to obtain a pointer to the class object’s IClassFactory interface and use its methods as needed. In the latter case, you should use the CoGetClassObject function.
In the CLSCTX enumeration, you can specify the type of server used to manage the object. The constants can be CLSCTX_INPROC_SERVER, CLSTCTX_INPROC_HANDLER, CLSCTX_LOCAL_SERVER, or any combination of these values. The constant CLSCTX_ALL is defined as the combination of all three. For more information about the use of one or a combination of these constants, refer to CLSCTX.

See Also

CoGetClassObject, IClassFactory::CreateInstance, CoCreateInstanceEx, CLSCTX, Instance Creation Helper Functions


CoCreateInstanceEx

Creates an instance of a specific class on a specific machine.

HRESULT CoCreateInstanceEx(

    REFCLSID  rclsid,      	    // CLSID of the object to be created
    IUnknown *  punkOuter,	    // If part of an aggregate, the controlling IUnknown
    DWORD  dwClsCtx,	          // CLSCTX values
    COSERVERINFO* pServerInfo,      // Machine on which the object is to be instantiated
    ULONG  cmq,	                 // Number of MULTI_QI structures in rgmqResults
    MULTI_QI  rgmqResults	           // Array of MULTI_QI structures
   );
Parameters

rclsid

[in] CLSID of the object to be created.

punkOuter

[in] When non-NULL, indicates the instance is being created as part of an aggregate, and punkOuter is to be used as the new instance’s controlling IUnknown. Aggregation is currently not supported cross-process or cross-machine. When instantiating an object out of process, CLASS_E_NOAGGREGATION will be returned if punkOuter is non-NULL.

dwClsCtx

[in] Values taken from the CLSCTX enumeration.

pServerInfo

[in] Machine on which to instantiate the object. May be NULL, in which case the object is instantiated on the current machine or at the machine specified in the registry under the class’s RemoteServerName named-value, according to the interpretation of the dwClsCtx parameter. See the CLSCTX documentation for details).

cmq

[in] Number of MULTI_QI structures in rgmqResults. Must be greater than zero.

rgmqResults

Array of MULTI_QI structures. Each structure has three members: the identifier for a requested interface (pIID), the location to return the interface pointer (pItf) and the return value of the call to QueryInterface (hr).

Return Values

This function supports the standard return value E_INVALIDARG, as well as the following:

S_OK

Indicates success.

CO_S_NOTALLINTERFACES

At least one, but not all of the interfaces requested in the rgmqResults array were successfully retrieved. The hr field of each of the MULTI_QI structures in rgmqResults indicates with S_OK or E_NOINTERFACE whether the specific interface was returned.

E_NOINTERFACE

None of the interfaces requested in the rgmqResults array were successfully retrieved.

Remarks

CoCreateInstanceEx creates a single uninitialized object associated with the given CLSID on a specified remote machine. This is an extension of the function CoCreateInstance, which creates an object on the local machine only. In addition, rather than requesting a single interface and obtaining a single pointer to that interface, CoCreateInstanceEx makes it possible to specify an array of structures, each pointing to an interface identifier (IID) on input, and, on return, containing (if available) a pointer to the requested interface and the return value of the QueryInterface call for that interface. This permits fewer round trips between machines.

The CoCreateInstanceEx helper function encapsulates three calls: first, to CoGetClassObject to connect to the class object associated with the specified CLSID, specifying the machine location of the class; second, to IClassFactory::CreateInstance to create an uninitialized instance, and finally, to IClassFactory::Release, to release the class object.
The object so created must still be initialized through a call to one of the initialization interfaces (such as IPersistStorage:::Load). The two helper functions, CoGetInstanceFromFile and CoGetInstanceFromIStorage encapsulate both the instance creation and initialization from the obvious sources.

See Also

CoGetInstanceFromFile, CoGetInstanceFromIStorage, CLSCTX, COSERVERINFO, Instance Creation Helper Functions


CoRevokeClassObject

Informs OLE that a class object, previously registered with the CoRegisterClassObject function, is no longer available for use.

HRESULT CoRevokeClassObject(

    DWORD dwRegister              	//Token on class object
   );
Parameter

dwRegister

[in] Token previously returned from the CoRegisterClassObject function.

Return Values

This function supports the standard return values E_INVALIDARG,

E_OUTOFMEMORY, and E_UNEXPECTED, as well as the following:

S_OK

The class object was successfully revoked.

Remarks

A successful call to CoRevokeClassObject means that the class object has been removed from the global class object table (although it does not release the class object). If other clients still have pointers to the class object and have caused the reference count to be incremented by calls to IUnknown::AddRef, the reference count will not be zero. When this occurs, applications may benefit if subsequent calls (with the obvious exceptions of IUnknown::AddRef and IUnknown::Release) to the class object fail.

An object application must call CoRevokeClassObject to revoke registered class objects before exiting the program. Class object implementers should call CoRevokeClassObject as part of the release sequence. You must specifically revoke the class object even when you have specified the flags value REGCLS_SINGLEUSE in a call to CoRegisterClassObject, indicating that only one application can connect to the class object.

See Also

CoGetClassObject, CoRegisterClassObject


µDico MASM Flottant 4.4

CoGetClassObject

Provides a pointer to an interface on a class object associated with a specified CLSID. CoGetClassObject locates, and if necessary, dynamically loads the executable code required to do this.

Call CoGetClassObject directly when you want to create multiple objects through a class object for which there is a CLSID in the system registry. You can also retrieve a class object from a specific remote machine. Most class objects implement the IClassFactory interface. You would then call IClassFactory::CreateInstance to create an uninitialized object. It is not always necessary to go through this process. To create a single object, call instead the either the CoCreateInstanceEx function, which allows you to create an instance on a remote machine. This replaces the CoCreateInstance function, which can still be used to create an instance on a local machine. Both functions encapsulate connecting to the class object, creating the instance, and releasing the class object. Two other functions, CoGetInstanceFromFile and CoGetInstanceFromIStorage, provide both instance creation on a remote system, and object activation. OLE also provides many other ways to create an object in the form of numerous helper functions and interface methods whose function is to create objects of a single type and provide a pointer to an interface on that object.

STDAPI CoGetClassObject(

    REFCLSID rclsid,            	//CLSID associated with the class object
    DWORD dwClsContext,	             //Context for running executable code
    COSERVERINFO * pServerInfo,	       //Pointer to machine on which the object is to be instantiated
    REFIID riid,                	//Reference to the identifier of the interface
    LPVOID * ppv	                   //Indirect pointer to the interface
   );
Parameters

rclsid

[in] CLSID associated with the data and code that you will use to create the objects.

dwClsContext

[in] Context in which the executable code is to be run. To enable a remote activation, CLSCTX_REMOTE_SERVER must be included. For more information on the context values and their use, see the CLSCTX enumeration.

pServerInfo

[in] Pointer to machine on which to instantiate the class object. May be NULL, in which case the class object is instantiated on the current machine or at the machine specified under the class’s RemoteServerName key in the registry, according to the interpretation of the dwClsCtx parameter (see the CLSCTX documentation for details).

riid

[in] Reference to the identifier of the interface, which will be supplied in ppv on successful return. This interface will be used to communicate with the class object. Typically this value is IID_IClassFactory, although other values - such as IID_IClassFactory2 which supports a form of licensing - are allowed. All OLE-defined interface IIDs are defined in the OLE header files as IID_interfacename, where interfacename is the name of the interface.

ppv

[out] On successful return, indirect pointer to the requested interface.

Return Values

S_OK

Location and connection to the specified class object was successful.

REGDB_E_CLASSNOTREG

CLSID is not properly registered. Can also indicate that the value you specified in dwClsContext is not in the registry.

E_NOINTERFACE

Either the object pointed to by ppv does not support the interface identified by riid, or the QueryInterface operation on the class object returned E_NOINTERFACE.

REGDB_E_READREGDB

Error reading the registration database.

CO_E_DLLNOTFOUND

In-process DLL or handler DLL not found (depends on context).

CO_E_APPNOTFOUND

EXE not found (CLSCTX_LOCAL_SERVER only).

E_ACCESSDENIED

General access failure (returned from LoadLib/CreateProcess).

CO_E_ERRORINDLL

EXE has error in image.

CO_E_APPDIDNTREG

EXE was launched, but it didn’t register class object (may or may not have shut down).

Remarks

A class object in OLE is an intermediate object that supports an interface that permits operations common to a group of objects. The objects in this group are instances derived from the same object definition represented by a single CLSID. Usually, the interface implemented on a class object is IClassFactory, through which you can create object instances of a given definition (class).
A call to CoGetClassObject creates, initializes, and gives the caller access (through a pointer to an interface specified with the riid parameter) to the class object. The class object is the one associated with the CLSID that you specify in the rclsid parameter. The details of how the system locates the associated code and data within a given machine are transparent to the caller, as is the dynamic loading of any code that is not already loaded.

If the class context is CLSCTX_REMOTE_SERVER, indicating remote activation is required, the COSERVERINFO structure provided in the pServerInfo parameter allows you to specify the machine on which the server is located. For information on the algorithm used to locate a remote server when pServerInfo is NULL, refer to the CLSCTX enumeration.
There are two places to find a CLSID for a given class:

·	The registry holds an association between CLSIDs and file suffixes, and between CLSIDs and file signatures for determining the class of an object.
·	When an object is saved to persistent storage, its CLSID is stored with its data.

To create and initialize embedded or linked OLE document objects, it is not necessary to call CoGetClassObject directly. Instead, call one of the OleCreate or OleCreateXxx helper functions. These functions encapsulate the entire object instantiation and initialization process, and call, among other functions, CoGetClassObject.
The riid parameter specifies the interface the client will use to communicate with the class object. In most cases, this interface is IClassFactory. This provides access to the IClassFactory::CreateInstance method, through which the caller can then create an uninitialized object of the kind specified in its implementation. All classes registered in the system with a CLSID must implement IClassFactory.

In rare cases, however, you may want to specify some other interface that defines operations common to a set of objects. For example, in the way OLE implements monikers, the interface on the class object is IParseDisplayName, used to transform the display name of an object into a moniker.
The dwClsContext parameter specifies the execution context, allowing one CLSID to be associated with different pieces of code in different execution contexts. The CLSCTX enumeration, defined in Compobj.H, specifies the available context flags. CoGetClassObject consults (as appropriate for the context indicated) both the registry and the class objects that are currently registered by calling the CoRegisterClassObject function.

To release a class object, use the class object’s Release method. The function CoRevokeClassObject is to be used only to remove a class object’s CLSID from the system registry.

See Also

CoCreateInstanceEx, CoRegisterClassObject, CoRevokeClassObject, OleLoad, CLSCTX, Creating an Object through a Class Object


µDico MASM Flottant 4.4

CoRegisterClassObject

Registers an EXE class object with OLE so other applications can connect to it. EXE object applications should call CoRegisterClassObject on startup. It can also be used to register internal objects for use by the same EXE or other code (such as DLLs) that the EXE uses.

STDAPI CoRegisterClassObject(

    REFCLSID rclsid,        	  //Class identifier (CLSID) to be registered
    IUnknown * pUnk,	        //Pointer to the class object
    DWORD dwClsContext,          //Context for running executable code
    DWORD flags,	              //How to connect to the class object
    LPDWORD * lpdwRegister	 //Pointer to the value returned
   );
Parameters

rclsid

[in] CLSID to be registered.

pUnk

[in] Pointer to theIUnknown interface on the class object whose availability is being published.

dwClsContext

[in] Context in which the executable code is to be run. For information on these context values, see the CLSCTX enumeration.

flags

[in] How connections are made to the class object. For information on these flags, see the REGCLS enumeration.

lpdwRegister

[out] Pointer to a value that identifies the class object registered; later used by the CoRevokeClassObject function to revoke the registration.

Return Values

This function supports the standard return values E_INVALIDARG, E_OUTOFMEMORY, and E_UNEXPECTED, as well as the following:

S_OK

The class object was registered successfully.

CO_E_OBJISREG

Already registered in the class object table.

Remarks

Only EXE object applications call CoRegisterClassObject. Object handlers or DLL object applications do not call this function ¾ instead, they must implement and export the DllGetClassObject function.
At startup, a multiple-use EXE object application must create a class object (with the IClassFactory interface on it), and call CoRegisterClassObject to register the class object. Object applications that support several different classes (such as multiple types of embeddable objects) must allocate and register a different class object for each.

Multiple registrations of the same class object are independent and do not produce an error. Each subsequent registration yields a unique key in lpdwRegister.
Multiple document interface (MDI) applications must register their class objects. Single document interface (SDI) applications must register their class objects only if they can be started by means of the /Embedding switch.
The server for a class object should call CoRevokeClassObject to revoke the class object (remove its registration) when all of the following are true:

·	There are no existing instances of the object definition
·	There are no locks on the class object
·	The application providing services to the class object is not under user control (not visible to the user on the display).

After the class object is revoked, when its reference count reaches zero, the class object can be released, allowing the application to exit.
For information on the flags parameter, refer to the REGCLS enumeration.

See Also

CoGetClassObject, CoRevokeClassObject, DllGetClassObject, REGCLS, CLSCTX


IUnknown

Header file Unknwn.h

Returns a pointer to a specified interface on an object to which a client currently holds an interface pointer.
This function must call IUnknown::AddRef on the pointer it returns.

The IUnknown interface lets clients get pointers to other interfaces on a given object through the QueryInterface method, and manage the existence of the object through the IUnknown::AddRef and IUnknown::Release methods. All other COM interfaces are inherited, directly or indirectly, from IUnknown. Therefore, the three methods in IUnknown are the first entries in the VTable for every interface.

When to Implement

You must implement IUnknown as part of every interface. If you are using C++ multiple inheritance to implement multiple interfaces, the various interfaces can share one implementation of IUnknown
. If you are using nested classes to implement multiple interfaces, you must implement IUnknown once for each interface you implement.

When to Use

Use IUnknown methods to switch between interfaces on an object, add references, and release objects.

Methods in Vtable Order

IUnknown Methods	Description
QueryInterface	Returns pointers to supported interfaces.
AddRef	Increments reference count.
Release	Decrements reference count.


IUnknown::AddRef

The IUnknown::AddRef method increments the reference count for an interface on an object. It should be called for every new copy of a pointer to an interface on a given object.

ULONG AddRef(void);

Return Value

Returns an integer from 1 to n, the value of the new reference count. This information is meant to be used for diagnostic/testing purposes only, because, in certain situations, the value may be unstable.

Remarks

Objects use a reference counting mechanism to ensure that the lifetime of the object includes the lifetime of references to it. You use IUnknown::AddRef to stabilize a copy of an interface pointer. It can also be called when the life of a cloned pointer must extend beyond the lifetime of the original pointer. The cloned pointer must be released by calling IUnknown::Release.
Objects must be able to maintain (2 (31) )-1 outstanding pointer references. Therefore, the internal reference counter that IUnknown::AddRef maintains must be a 32-bit unsigned integer.

Notes to Callers

Call this function for every new copy of an interface pointer that you make. For example, if you are passing a copy of a pointer back from a function, you must call IUnknown::AddRef on that pointer. You must also call IUnknown::AddRef on a pointer before passing it as an in-out parameter to a function; the function will call IUnknown::Release before copying the out-value on top of it.

See Also

IUnknown::Release


IUnknown::QueryInterface

Returns a pointer to a specified interface on an object to which a client currently holds an interface pointer. This function must call IUnknown::AddRef on the pointer it returns.

HRESULT QueryInterface(

    REFIID iid,	//Identifier of the requested interface
    void ** ppvObject	//Indirect pointer to the object
   );
Parameters

iid

[in] Identifier of the interface being requested.

ppvObject

[out] Indirectly points to the interface specified in iid. If the object does not support the interface specified in iid, *ppvObject is set to NULL.

Return Value

S_OK if the interface is supported, E_NOINTERFACE if not.

Remarks

The QueryInterface method gives a client access to other interfaces on an object.
For any one object, a specific query for the IUnknown interface on any of the object’s interfaces must always return the same pointer value. This allows a client to determine whether two pointers point to the same component by calling QueryInterface on both and comparing the results. It is specifically not the case that queries for interfaces (even the same interface through the same pointer) must return the same pointer value.

There are four requirements for implementations of QueryInterface (In these cases, “must succeed” means “must succeed barring catastrophic failure.”):

·	The set of interfaces accessible on an object through IUnknown::QueryInterface must be static, not dynamic. This means that if a call to QueryInterface for a pointer to a specified interface succeeds the first time, it must succeed again, and if it fails the first time, it must fail on all subsequent queries.
·	It must be symmetric ¾ if a client holds a pointer to an interface on an object, and queries for that interface, the call must succeed.

·	It must be reflexive ¾ if a client holding a pointer to one interface queries successfully for another, a query through the obtained pointer for the first interface must succeed.
·	It must be transitive ¾ if a client holding a pointer to one interface queries successfully for a second, and through that pointer queries successfully for a third interface, a query for the first interface through the pointer for the third interface must succeed.


IUnknown::Release

Decrements the reference count for the calling interface on a object. If the reference count on the object falls to 0, the object is freed from memory.

ULONG Release(void);

Return Value

Returns the resulting value of the reference count, which is used for diagnostic/testing purposes only. If you need to know that resources have been freed, use an interface with higher-level semantics.

Remarks

If IUnknown::AddRef has been called on this object’s interface n times and this is the n+1th call to IUnknown::Release, the implementation of IUnknown::AddRef must cause the interface pointer to free itself. When the released pointer is the only existing reference to an object (whether the object supports single or multiple interfaces), the implementation must free the object.

Note  Aggregation of objects restricts the ability to recover interface pointers.

Notes to Callers

Call this function when you no longer need to use an interface pointer. If you are writing a function that takes an in-out parameter, call IUnknown::Release on the pointer you are passing in before copying the out-value on top of it.

See Also

IUnknown::AddRef


CreateInstance

Creates an uninitialized object.

HRESULT CreateInstance(

    IUnknown * pUnkOuter,	//Pointer to whether object is or isn’t part of an aggregate
    REFIID riid,	//Reference to the identifier of the interface
    void ** ppvObject	//Indirect pointer to the interface
   );

Parameters

pUnkOuter

[in] If the object is being created as part of an aggregate, pointer to the controlling IUnknown interface of the aggregate. Otherwise, pUnkOuter must be NULL.

riid

[in] Reference to the identifier of the interface to be used to communicate with the newly created object. If pUnkOuter is NULL, this parameter is frequently the IID of the initializing interface; if pUnkOuter is non-NULL, riid must be IID_IUnknown (defined in the header as the IID for IUnknown).

ppvObject

[out] Indirect pointer to the requested interface. If the object does not support the interface specified in riid, ppvObject must be set to NULL.

Return Values

This method supports the standard return values E_UNEXPECTED, E_OUTOFMEMORY, and E_INVALIDARG, as well as the following:

S_OK

The specified object was created.

CLASS_E_NOAGGREGATION

The pUnkOuter parameter was non-NULL and the object does not support aggregation.

E_NOINTERFACE

The object that ppvObject points to does not support the interface identified by riid.

Remarks

The IClassFactory interface is always on a class object. The CreateInstance method creates an uninitialized object of the class identified with the specified CLSID. When an object is created in this way, the CLSID must be registered in the system registry with CoRegisterClassObject.
The pUnkOuter parameter indicates whether the object is being created as part of an aggregate. Object definitions are not required to support aggregation ¾ they must be specifically designed and implemented to support it.

The riid parameter specifies the IID (interface identifier) of the interface through which you will communicate with the new object. If pUnkOuter is non-NULL (indicating aggregation), the value of the riid parameter must be IID_IUnknown. If the object is not part of an aggregate, riid often specifies the interface though which the object will be initialized.
For OLE embeddings, the initialization interface is IPersistStorage, but in other situations, other interfaces are used. To initialize the object, there must be a subsequent call to an appropriate method in the initializing interface. Common initialization functions include IPersistStorage::InitNew (for new, blank embeddable components), IPersistStorage::Load (for reloaded embeddable components), IPersistStream::Load, (for objects stored in a stream object) or IPersistFile::Load (for objects stored in a file).

In general, if an application supports only one class of objects, and the class object is registered for single use, only one object can be created. The application must not create other objects, and a request to do so should return an error from IClassFactory::CreateInstance. The same is true for applications that support multiple classes, each with a class object registered for single use; a CreateInstance for one class followed by a CreateInstance for any of the classes should return an error.

To avoid returning an error, applications that support multiple classes with single-use class objects can revoke the registered class object of the first class by calling CoRevokeClassObject when a request for instantiating a second is received. For example, suppose there are two classes, A and B. When IClassFactory::CreateInstance is called for class A, revoke the class object for B. When B is created, revoke the class object for A. This solution complicates shutdown because one of the class objects might have already been revoked (and cannot be revoked twice).

See Also

CoRegisterClassObject, CoRevokeClassObject, CoCreateInstance, CoGetClassObject


DllGetClassObject

Retrieves the class object from a DLL object handler or object application. DllGetClassObject is called from within the CoGetClassObject function when the class context is a DLL.

Note  OLE does not provide this function. DLLs that support the OLE Component Object Model (COM) must implement DllGetClassObject in OLE object handlers or DLL applications.

STDAPI DllGetClassObject(

    REFCLSID rclsid,	   //CLSID for the class object
    REFIID riid,	         //Reference to the identifier of the interface that communicates with the class object
    LPVOID  * ppv	         //Indirect pointer to the communicating interface
   );
Parameters

rclsid

[in] CLSID that will associate the correct data and code.

riid

[in] Reference to the identifier of the interface that the caller is to use to communicate with the class object. Usually, this is IID_IClassFactory (defined in the OLE headers as the interface identifier for IClassFactory).

ppv

[out] Indirect pointer to the requested interface or, if an error occurs, to NULL.

Return Values

This function supports the standard return values E_INVALIDARG, E_OUTOFMEMORY and E_UNEXPECTED, as well as the following:

S_OK

The object was retrieved successfully.

CLASS_E_CLASSNOTAVAILABLE

The DLL does not support the class (object definition).

Remarks

If a call to the CoGetClassObject function finds the class object that is to be loaded in a DLL, CoGetClassObject uses the DLL’s exported DllGetClassObject function.

Notes to Callers

You should not call DllGetClassObject directly. When an object is defined in a DLL, CoGetClassObject calls the CoLoadLibrary function to load the DLL, which, in turn, calls DllGetClassObject.

Notes to Implementers

You need to implement DllGetClassObject in (and export it from) DLLs that support the OLE Component Object Model.

Example

Following is an example (in C++) of an implementation of DllGetClassObject. In this example, DllGetClassObject creates a class object and calls its QueryInterface method to retrieve a pointer to the interface requested in riid. The implementation safely releases the reference it holds to the IClassFactory interface because it returns a reference-counted pointer to IClassFactory to the caller.

HRESULT_export  PASCAL DllGetClassObject
        (REFCLSID rclsid, REFIID riid, LPVOID * ppvObj)
{
    HRESULT hres = E_OUTOFMEMORY;
    *ppvObj = NULL;

    CClassFactory *pClassFactory = new CClassFactory(rclsid);
    if (pClassFactory != NULL)   {
        hRes = pClassFactory->QueryInterface(riid, ppvObj);
        pClassFactory->Release();
    }
    return hRes;
}

See Also

CoGetClassObject, DllCanUnloadNow


DllCanUnloadNow

Determines whether the DLL that implements this function is in use. If not, the caller can safely unload the DLL from memory.

Note  OLE does not provide this function. DLLs that support the OLE Component Object Model (COM) should implement and export DllCanUnloadNow.

STDAPI DllCanUnloadNow();

Return Values

S_OK

The DLL can be unloaded.

S_FALSE

The DLL cannot be unloaded now.

Remarks

A call to DllCanUnloadNow determines whether the DLL from which it is exported is still in use. A DLL is no longer in use when it is not managing any existing objects (the reference count on all of its objects is 0).

Notes to Callers

You should not have to call DllCanUnloadNow directly. OLE calls it only through a call to the CoFreeUnusedLibraries function. When it returns S_OK, CoFreeUnusedLibraries safely frees the DLL.

Notes to Implementers

You need to implement DllCanUnloadNow in, and export it from, DLLs that are to be dynamically loaded through a call to the CoGetClassObject function. (You also need to implement and export the DllGetClassObject function in the same DLL).
If a DLL loaded through a call to CoGetClassObject fails to export DllCanUnloadNow, the DLL will not be unloaded until the application calls the CoUninitialize function to release the OLE libraries.

If the DLL links to another DLL, returning S_OK from DllCanUnloadNow will also cause the second, dependent DLL to be unloaded. To eliminate the possibility of a crash, the primary DLL should call the CoLoadLibrary function, specifying the path to the second DLL as the first parameter, and setting the auto free parameter to TRUE. This forces the COM library to reload the second DLL and set it up for a call to CoFreeUnusedLibraries to free it separately when appropriate.

DllCanUnloadNow should return S_FALSE if there are any existing references to objects that the DLL manages.

See Also

DllGetClassObject